.NET Core  

Benchmarking JSON Serialization Options in .NET 11

JSON serialization is a core part of modern .NET applications. Whether you're building REST APIs, microservices, background services, or message-driven systems, converting objects to and from JSON happens thousands—or even millions—of times every day.

A small improvement in serialization performance can reduce CPU usage, memory allocations, and response latency under high traffic. .NET provides several serialization approaches, each with different trade-offs in speed, memory consumption, and flexibility.

In this article, you'll compare the most common JSON serialization options in .NET 11, implement each approach, and learn how to benchmark them correctly without relying on fabricated performance numbers.

Note: This article explains implementation and benchmark methodology. Since the research brief does not include measured benchmark data or hardware specifications, no performance numbers are presented.

JSON Serialization in .NET

Serialization converts a .NET object into JSON.

Product Object
      │
      ▼
 JSON Serializer
      │
      ▼
{
  "id": 1,
  "name": "Laptop"
}

Deserialization performs the reverse operation by converting JSON back into .NET objects.

Available Serialization Options

Modern .NET applications typically use one of the following approaches.

SerializerPerformanceMemory UsageFlexibility
System.Text.JsonHighLowGood
System.Text.Json Source GenerationVery HighVery LowModerate
Newtonsoft.JsonGoodModerateExcellent

The right choice depends on your application's requirements.

Sample Model

We'll use the following model throughout the examples.

public class Product
{
    public int Id { get; set; }

    public string Name { get; set; } = "";

    public decimal Price { get; set; }

    public bool InStock { get; set; }
}

Using System.Text.Json

System.Text.Json is included with .NET and is the default serializer used by ASP.NET Core.

Serialize an object:

using System.Text.Json;

var product = new Product
{
    Id = 1,
    Name = "Laptop",
    Price = 999,
    InStock = true
};

string json =
    JsonSerializer.Serialize(product);

Deserialize:

var product =
    JsonSerializer.Deserialize<Product>(json);

Why Use It?

Advantages include:

  • High performance

  • Low memory allocations

  • Built into .NET

  • No external dependency

  • Native ASP.NET Core integration

It is the preferred choice for most new applications.

Using Source Generation

Source generation reduces runtime reflection by generating serialization code during compilation.

Create a context.

using System.Text.Json.Serialization;

[JsonSerializable(typeof(Product))]
public partial class ProductContext
    : JsonSerializerContext
{
}

Serialize using the generated context.

string json =
    JsonSerializer.Serialize(
        product,
        ProductContext.Default.Product);

Benefits

Source generation can reduce startup overhead and runtime allocations, particularly in applications that repeatedly serialize known types.

It is especially useful for:

  • Native AOT

  • High-throughput APIs

  • Microservices

  • Background processing

Using Newtonsoft.Json

Install the package.

dotnet add package Newtonsoft.Json

Serialize:

using Newtonsoft.Json;

string json =
    JsonConvert.SerializeObject(product);

Deserialize:

var product =
    JsonConvert.DeserializeObject<Product>(json);

When Should You Use It?

Newtonsoft.Json remains useful for scenarios involving:

  • Advanced converters

  • Dynamic JSON

  • LINQ to JSON

  • Legacy applications

  • Extensive customization

Many existing enterprise applications still depend on its mature feature set.

ASP.NET Core Integration

System.Text.Json is enabled by default.

To use Newtonsoft.Json:

builder.Services
    .AddControllers()
    .AddNewtonsoftJson();

Choose one serializer consistently throughout your application unless there is a specific compatibility requirement.

Source Generation Workflow

A simplified workflow looks like this:

Model
   │
   ▼
Source Generator
   │
Generated Serialization Code
   │
   ▼
Runtime Serialization

Generating serialization code at build time reduces work performed at runtime.

Benchmarking Methodology

The research brief requests serializer benchmarking but does not provide benchmark results or hardware details. Instead of presenting unsupported numbers, use the following methodology.

Test Environment

Keep the following consistent:

  • .NET SDK version

  • Operating system

  • Hardware

  • Build configuration

  • Garbage collector mode

Run benchmarks in Release mode.

Test Scenarios

Evaluate multiple workloads.

Small objects:

Product

Large collections:

List<Product>

Complex nested objects.

Large JSON payloads.

Serialization and deserialization should both be measured independently.

BenchmarkDotNet Setup

Install BenchmarkDotNet.

dotnet add package BenchmarkDotNet

Create a benchmark class.

using BenchmarkDotNet.Attributes;

[MemoryDiagnoser]
public class SerializationBenchmark
{
    private readonly Product _product =
        new()
        {
            Id = 1,
            Name = "Laptop",
            Price = 999
        };

    [Benchmark]
    public string SystemTextJson()
        => JsonSerializer.Serialize(_product);

    [Benchmark]
    public string Newtonsoft()
        => JsonConvert.SerializeObject(_product);
}

Run the benchmark.

BenchmarkRunner.Run<SerializationBenchmark>();

Metrics to Measure

Collect the following metrics:

  • Mean execution time

  • Median execution time

  • Throughput

  • Memory allocations

  • Allocated bytes

  • Garbage collections

  • Startup overhead

These metrics provide a more complete picture than execution time alone.

Comparing Serialization Options

FeatureSystem.Text.JsonSource GenerationNewtonsoft.Json
Built into .NETYesYesNo
ReflectionYesNoYes
Startup PerformanceGoodExcellentGood
Runtime PerformanceHighVery HighGood
Memory UsageLowVery LowModerate
Native AOT SupportYesExcellentLimited
Advanced JSON FeaturesModerateModerateExcellent

Production Considerations

Use Release Builds

Benchmark Debug builds can produce misleading results.

Always benchmark using Release configuration.

Warm Up the Application

Execute benchmarks after the runtime has completed JIT compilation.

BenchmarkDotNet automatically performs warm-up iterations before collecting measurements.

Test Realistic Payloads

Benchmark objects that resemble production data rather than simple demonstration models.

Small test objects rarely represent real application workloads.

Measure Both Serialization and Deserialization

Applications often spend significant time deserializing incoming requests in addition to serializing responses.

Benchmark both operations independently.

Best Practices

  • Prefer System.Text.Json for new applications.

  • Consider source generation for frequently serialized types.

  • Use Release builds when benchmarking.

  • Benchmark representative payload sizes.

  • Measure memory allocations alongside execution time.

  • Keep serialization options consistent across the application.

  • Avoid unnecessary object conversions.

  • Validate benchmark results on production-like hardware.

Common Mistakes

MistakeImpact
Benchmarking Debug buildsMisleading results
Measuring only serializationIncomplete evaluation
Ignoring memory allocationsHidden performance costs
Using unrealistic test objectsPoor production relevance
Mixing serializers unnecessarilyIncreased maintenance
Drawing conclusions from a single benchmarkInaccurate optimization decisions

Troubleshooting

Benchmark Results Vary

Verify:

  • Release build

  • Hardware consistency

  • Background processes

  • Warm-up execution

  • Benchmark configuration

Use BenchmarkDotNet defaults whenever possible to improve repeatability.

Memory Allocations Are High

Review:

  • Object size

  • Collection size

  • Serializer configuration

  • Intermediate object creation

Memory diagnostics are often as important as execution time.

Source Generation Doesn't Work

Ensure:

  • The context class is declared as partial.

  • [JsonSerializable] attributes include all required types.

  • The generated context is used during serialization.

FAQs

Should I always use System.Text.Json?

For most new .NET applications, yes. It offers excellent performance and is fully integrated with ASP.NET Core.

When should I use source generation?

Source generation is beneficial when serializing known types repeatedly, especially in high-throughput services or Native AOT applications.

Is Newtonsoft.Json obsolete?

No. It remains a mature and capable library, particularly for advanced JSON manipulation and legacy applications.

Why use BenchmarkDotNet?

BenchmarkDotNet automates warm-up, iteration control, memory measurement, and statistical analysis, making benchmark results more reliable.

Which metric is most important?

There is no single metric. Evaluate execution time, memory allocations, throughput, and garbage collection together to understand overall performance.

Conclusion

Choosing the right JSON serialization strategy can have a measurable impact on application performance, especially in APIs and services that process large volumes of requests. System.Text.Json is the recommended default for most modern .NET applications, while source generation offers additional optimization for known types and Native AOT scenarios. Newtonsoft.Json remains valuable for advanced serialization requirements and legacy compatibility.

Rather than relying on generic performance claims, benchmark serializers using representative workloads, production-like environments, and tools such as BenchmarkDotNet. This approach ensures optimization decisions are based on reliable measurements instead of assumptions.