System.Text.Json
The JSON stack built into modern .NET, with source generation and a low-allocation design.
Decision Guide
Most new .NET applications should start by checking whether System.Text.Json covers their requirements. Newtonsoft.Json remains a pragmatic choice when its mature feature set, established converters or existing models are part of the project.
The decision to make
Should a new project use System.Text.Json or Newtonsoft.Json?
Curated starting points to compare. Always evaluate suitability against your specific project requirements.
The JSON stack built into modern .NET, with source generation and a low-allocation design.
A long-established JSON library with a broad converter and compatibility ecosystem.
A focused library for evaluating JSON Schema rather than replacing an application serializer.
Start with a small, representative feature and validate the package against the actual target framework and deployment environment.
Write contract-focused tests for the required behavior before rolling the choice across the solution.
Document package version, configuration, operational ownership, and an explicit rollback plan in the change record.
Risk
Checklist
By NuBrowse · Updated
A successful deserialization can still lose values. This example checks one customer payload before and after migration, then verifies an explicit wire-name mapping. Run it before replacing the serializer in a background worker or console application.
The direct System.Text.Json defaults match property names case-sensitively. Web options enable case-insensitive matching and quoted numbers. ASP.NET Core uses web defaults, so test your actual host configuration. Replace Newtonsoft.Json property attributes with the corresponding System.Text.Json attributes when preserving a wire contract.
Create a console project, replace Program.cs with the example, and run it. Verification target: .NET 10 and Newtonsoft.Json 13.0.4. This is a compatibility check, not a performance benchmark.
dotnet new console -n JsonMigrationCheck -f net10.0
cd JsonMigrationCheck
dotnet add package Newtonsoft.Json --version 13.0.4
# Replace Program.cs with the code below.
dotnet runusing System.Text.Json;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
const string payload = """{"name":"Ada","age":"37"}""";
var legacy = JsonConvert.DeserializeObject<Person>(payload)!;
var defaults = System.Text.Json.JsonSerializer.Deserialize<Person>(payload)!;
var web = System.Text.Json.JsonSerializer.Deserialize<Person>(
payload, new JsonSerializerOptions(JsonSerializerDefaults.Web))!;
Check("Newtonsoft defaults", legacy.Name == "Ada" && legacy.Age == 37);
Check("System.Text.Json defaults", defaults.Name is null && defaults.Age == 0);
Check("System.Text.Json web options", web.Name == "Ada" && web.Age == 37);
const string renamed = """{"full_name":"Ada"}""";
var mapped = System.Text.Json.JsonSerializer.Deserialize<RenamedPerson>(renamed)!;
Check("Explicit property mapping", mapped.Name == "Ada");
static void Check(string label, bool passed)
{
if (!passed) throw new InvalidOperationException(label);
Console.WriteLine($"PASS: {label}");
}
public sealed class Person
{
public string? Name { get; set; }
public int Age { get; set; }
}
public sealed class RenamedPerson
{
[JsonPropertyName("full_name")]
public string? Name { get; set; }
}PASS: Newtonsoft defaults
PASS: System.Text.Json defaults
PASS: System.Text.Json web options
PASS: Explicit property mappingAdd captured, anonymized payloads and assert every required field. Include nulls, enums, custom converters and reference cycles. Verify outgoing property names against stored fixtures too. Keep Newtonsoft.Json for contracts that fail until their differences are understood and covered by tests.
Sources: Microsoft migration reference and property-name matching.
Re-evaluate the choice when changing target frameworks, introducing Native AOT, or integrating an API with a fixed JSON contract.