C# JSON Serialization Guide

C# ~4 min read
C#JSONSystem.Text.JsonNewtonsoft.JsonSerialization

Systematic introduction to System.Text.Json and Newtonsoft.Json serialization techniques, including naming policies, camelCase conversion, dynamic object handling, and Source Generator acceleration.

System.Text.Json vs Newtonsoft.Json

FeatureSystem.Text.JsonNewtonsoft.Json
PerformanceExcellentGood
AOT SupportYesLimited
Built-inYes (.NET Core 3.0+)Third-party
Streaming APIYesYes
PolymorphismGood (.NET 7+)Excellent
CommunityGrowingMature

Note: .NET 8+ enhances polymorphism further with custom type discriminators via IJsonTypeInfoResolver, allowing custom logic for resolving derived types at runtime without relying solely on [JsonDerivedType] attributes.

System.Text.Json Basics

using System.Text.Json;
using System.Text.Json.Serialization;

var user = new User { Name = "Alice", Age = 30 };

// Serialize
string json = JsonSerializer.Serialize(user, new JsonSerializerOptions {
    WriteIndented = true,
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});

// Deserialize
var parsed = JsonSerializer.Deserialize<User>(json);

JsonSerializerOptions

var options = new JsonSerializerOptions {
    // Naming
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,

    // Null handling
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,

    // Numbers
    NumberHandling = JsonNumberHandling.AllowReadingFromString,

    // Formatting
    WriteIndented = true,

    // Trailing commas
    AllowTrailingCommas = true,

    // Comments
    ReadCommentHandling = JsonCommentHandling.Skip,
};

Attributes

public class User
{
    [JsonPropertyName("user_name")]
    public string Name { get; set; }

    [JsonIgnore]
    public string Password { get; set; }

    [JsonConverter(typeof(JsonStringEnumConverter))]
    public UserRole Role { get; set; }

    [JsonInclude]
    internal string InternalNote { get; set; }

    [JsonPropertyOrder(1)]
    public int Priority { get; set; }
}

Polymorphic Serialization (.NET 7+)

[JsonDerivedType(typeof(Employee), typeDiscriminator: "employee")]
[JsonDerivedType(typeof(Manager), typeDiscriminator: "manager")]
public class Person
{
    public string Name { get; set; }
}

public class Employee : Person
{
    public string Department { get; set; }
}

public class Manager : Person
{
    public string[] DirectReports { get; set; }
}

Source Generators for AOT

Source generators compile serialization logic at build time, eliminating runtime reflection.

[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(List<User>))]
internal partial class UserJsonContext : JsonSerializerContext { }

// Usage with AOT
string json = JsonSerializer.Serialize(user, UserJsonContext.Default.User);
var parsed = JsonSerializer.Deserialize(json, UserJsonContext.Default.User);

Custom Converters

public class DateTimeConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTime.ParseExact(reader.GetString()!, "yyyy-MM-dd", CultureInfo.InvariantCulture);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("yyyy-MM-dd"));
    }
}

// Register globally
options.Converters.Add(new DateTimeConverter());

Newtonsoft.Json Features

Newtonsoft.Json offers more flexible polymorphic handling:

// Requires: Install-Package JsonSubTypes
[JsonConverter(typeof(JsonSubtypes), "Type")]
[JsonSubtypes.KnownSubType(typeof(Employee), "Employee")]
[JsonSubtypes.KnownSubType(typeof(Manager), "Manager")]
public class Person
{
    public string Name { get; set; }
    public virtual string Type => nameof(Person);
}

// Or use built-in TypeNameHandling (with security caveat):
var settings = new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.Auto // Use with caution
};

Json.NET Settings

var settings = new JsonSerializerSettings {
    NullValueHandling = NullValueHandling.Ignore,
    DefaultValueHandling = DefaultValueHandling.Ignore,
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    Formatting = Formatting.Indented,
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
};

string json = JsonConvert.SerializeObject(user, settings);
var parsed = JsonConvert.DeserializeObject<User>(json, settings);

Migration from Newtonsoft to System.Text.Json

  1. Replace JsonConvert.Serialize/Deserialize with JsonSerializer.Serialize/Deserialize
  2. Replace [JsonProperty("name")] with [JsonPropertyName("name")]
  3. Replace [JsonIgnore] — same name but different namespace
  4. Add JsonNamingPolicy.CamelCase instead of CamelCasePropertyNamesContractResolver
  5. Replace custom JsonConverter<T> implementations
  6. Test polymorphic scenarios — System.Text.Json requires explicit [JsonDerivedType]
// Before (Newtonsoft)
[JsonProperty("user_name")]
public string Name { get; set; }

// After (System.Text.Json)
[JsonPropertyName("user_name")]
public string Name { get; set; }

Performance Tips

  1. Reuse JsonSerializerOptions — Creating a new instance is expensive
  2. Use Utf8JsonWriter/Reader for maximum performance
  3. Enable source generators for AOT scenarios
  4. Use JsonDocument for DOM-style access without full deserialization
  5. Pool buffers with ArrayPool<byte>.Shared
// Utf8JsonWriter for zero-allocation serialization
var buffer = new ArrayBufferWriter<byte>();
using var writer = new Utf8JsonWriter(buffer);
writer.WriteStartObject();
writer.WriteString("name", "Alice");
writer.WriteNumber("age", 30);
writer.WriteEndObject();
writer.Flush();
ReadOnlySpan<byte> json = buffer.WrittenSpan;

Dynamic Object Handling

Working with unknown JSON structures:

using System.Text.Json.Nodes;

// Parse to JsonNode for dynamic access
JsonNode node = JsonNode.Parse(jsonString);
string name = node["name"].GetValue<string>();
int age = node["age"].GetValue<int>();

// Mutable - can modify and re-serialize
node["email"] = "new@example.com";
string modified = node.ToJsonString();

JsonNode API (.NET 6+)

JsonNode is the modern mutable DOM for working with dynamic JSON:

using System.Text.Json.Nodes;

// Create from string
JsonNode node = JsonNode.Parse(jsonString);

// Access values
string name = node["name"].GetValue<string>();
int age = node["age"].GetValue<int>();
JsonArray tags = node["tags"].AsArray();

// Modify
node["email"] = "updated@example.com";
node["tags"].AsArray().Add("new-tag");

// Serialize back
string modified = node.ToJsonString(new JsonSerializerOptions { WriteIndented = true });

JsonNode is preferred over JsonDocument when you need to modify the JSON structure.

HttpClient Integration

JSON is the standard for REST API communication:

using System.Net.Http.Json;

// GET request
var users = await httpClient.GetFromJsonAsync<List<User>>("/api/users");

// POST request
var response = await httpClient.PostAsJsonAsync("/api/users", newUser);
response.EnsureSuccessStatusCode();

// PATCH request
var patchDoc = new { name = "Updated Name" };
var patchResponse = await httpClient.PatchAsJsonAsync($"/api/users/{id}", patchDoc);

JSON Schema Validation

Using JsonSchema.Net:

using Json.Schema;

var schema = JsonSchema.FromFile("schema.json");
var instance = JsonDocument.Parse("{\"name\": \"Alice\", \"age\": 30}");

var result = schema.Validate(instance.RootElement);
if (!result.IsValid)
{
    foreach (var error in result.NestedResults)
    {
        Console.WriteLine(error.Message);
    }
}

JSON API Comparison Across Languages

Language Library Serialize Deserialize Pretty Print Config
Python json(内置) json.dumps(obj) json.loads(str) json.dumps(obj, indent=2) ensure_ascii, default, cls
JavaScript JSON(内置) JSON.stringify(obj) JSON.parse(str) JSON.stringify(obj, null, 2) replacer, space
Java Jackson / Gson mapper.writeValueAsString(obj) mapper.readValue(str, Class.class) mapper.writerWithDefaultPrettyPrinter() 注解、Module、Feature
Go encoding/json(内置) json.Marshal(obj) json.Unmarshal(data, &obj) json.MarshalIndent(obj, "", " ") struct tag
C# System.Text.Json(内置) JsonSerializer.Serialize(obj) JsonSerializer.Deserialize<T>(str) WriteIndented = true JsonSerializerOptions
TypeScript JSON(内置) JSON.stringify(obj) JSON.parse(str) JSON.stringify(obj, null, 2) replacer, space
Rust serde_json serde_json::to_string(&obj) serde_json::from_str::<T>(str) serde_json::to_string_pretty(&obj) serde attributes, custom (de)serializers

Frequently Asked Questions (FAQ)

How to handle datetime types in JSON serialization?

Most language JSON libraries do not support datetime types by default. The common approach is to serialize as ISO 8601 format strings (e.g. "2026-07-15T10:30:00Z") or Unix timestamps, then convert back to datetime objects during deserialization.

How to ignore null values or empty fields in JSON serialization?

Different languages have different implementations: Python can filter None values in custom encoders; JavaScript can use replacer functions; Java Jackson uses @JsonInclude annotations; Go uses omitempty struct tags; C# sets DefaultIgnoreCondition.

How to handle circular references in JSON serialization?

Circular references are a common pitfall. Standard JSON libraries usually throw exceptions or cause stack overflow. Solutions include using @JsonIdentityInfo (Java Jackson), implementing custom serializers, or converting to DTOs before serialization.

Why are my private fields not being serialized?

JSON serialization libraries typically only serialize public fields or properties with getters. This is a security consideration. You can use annotations like @JsonProperty (Java Jackson) or [JsonInclude] (C#) to include non-public members.

What are the performance optimization tips for JSON serialization?

1) Reuse serializer instances; 2) Use streaming APIs for large data; 3) Use compile-time generation (C# Source Generator, Go easyjson); 4) Avoid excessive nesting; 5) Use database pagination for large JSON.

Recommended Tools

These tools can help you handle JSON data more efficiently in C# development:

Related Articles