Skip to main content

Decision Guide

JSON libraries for .NET

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.

4decision criteria
3packages compared
4pre-launch checks

The decision to make

Should a new project use System.Text.Json or Newtonsoft.Json?

  • Target frameworks and whether the library is included in the shared framework.
  • Required serializer behavior: polymorphism, reference handling, custom converters and DOM APIs.
  • The migration cost of existing attributes, converters and tests.
  • Whether package dependencies or source-generation support matter for the deployment model.

Shortlist & Trade-offs

Curated starting points to compare. Always evaluate suitability against your specific project requirements.

1

System.Text.Json

The JSON stack built into modern .NET, with source generation and a low-allocation design.

Inspect package
Best fit: New .NET applications that target modern runtimes and can use its supported serializer model.
Validate first: Check behavior differences before migrating Newtonsoft.Json attributes, converters or reference handling.
2

Newtonsoft.Json

A long-established JSON library with a broad converter and compatibility ecosystem.

Inspect package
Best fit: Existing applications or integrations that already depend on Json.NET behavior and extension points.
Validate first: It adds a package dependency where System.Text.Json may already be available in the runtime.
3

JsonSchema.Net

A focused library for evaluating JSON Schema rather than replacing an application serializer.

Inspect package
Best fit: Validating JSON contracts at boundaries, configuration inputs or integration points.
Validate first: It complements a serializer; it is not a general System.Text.Json or Json.NET alternative.

Practical implementation plan

  1. 1

    Start with a small, representative feature and validate the package against the actual target framework and deployment environment.

  2. 2

    Write contract-focused tests for the required behavior before rolling the choice across the solution.

  3. 3

    Document package version, configuration, operational ownership, and an explicit rollback plan in the change record.

Risk

Common pitfalls

  • Choosing by popularity alone instead of validating compatibility, maintenance posture, and the application's actual constraints.
  • Adding overlapping packages that solve the same concern, increasing upgrade and incident-response effort.
  • Shipping default configuration without validating security, performance, and operational behavior in the deployed environment.

Checklist

Before production

  • The selected package version is compatible with supported target frameworks.
  • Configuration is reviewed for security, observability, and production defaults.
  • Representative success and failure paths are covered by automated tests.
  • License, advisory, and upgrade ownership are recorded for the dependency.

Newtonsoft.Json to System.Text.Json: a runnable migration check

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 run
using 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; }
}

Expected output

PASS: Newtonsoft defaults
PASS: System.Text.Json defaults
PASS: System.Text.Json web options
PASS: Explicit property mapping

Extend the check with your production contracts

Add 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.

When to revisit this choice

Re-evaluate the choice when changing target frameworks, introducing Native AOT, or integrating an API with a fixed JSON contract.

Put this decision into practice