System.Text.Json vs Newtonsoft.Json
| Feature | System.Text.Json | Newtonsoft.Json |
|---|---|---|
| Performance | Excellent | Good |
| AOT Support | Yes | Limited |
| Built-in | Yes (.NET Core 3.0+) | Third-party |
| Streaming API | Yes | Yes |
| Polymorphism | Good (.NET 7+) | Excellent |
| Community | Growing | Mature |
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
- Replace
JsonConvert.Serialize/DeserializewithJsonSerializer.Serialize/Deserialize - Replace
[JsonProperty("name")]with[JsonPropertyName("name")] - Replace
[JsonIgnore]— same name but different namespace - Add
JsonNamingPolicy.CamelCaseinstead ofCamelCasePropertyNamesContractResolver - Replace custom
JsonConverter<T>implementations - 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
- Reuse
JsonSerializerOptions— Creating a new instance is expensive - Use
Utf8JsonWriter/Readerfor maximum performance - Enable source generators for AOT scenarios
- Use
JsonDocumentfor DOM-style access without full deserialization - 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);
}
}
Related Resources
- JSON Best Practices — Universal guidelines for all languages
- JSON Schema Validation — Validate your JSON structures
- JSON Security — Protect against common vulnerabilities
- JSON Formatter — Format and validate your JSON online
- JSON Schema Validator — Test your schemas