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.
| Serializer | Performance | Memory Usage | Flexibility |
|---|
| System.Text.Json | High | Low | Good |
| System.Text.Json Source Generation | Very High | Very Low | Moderate |
| Newtonsoft.Json | Good | Moderate | Excellent |
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:
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
| Feature | System.Text.Json | Source Generation | Newtonsoft.Json |
|---|
| Built into .NET | Yes | Yes | No |
| Reflection | Yes | No | Yes |
| Startup Performance | Good | Excellent | Good |
| Runtime Performance | High | Very High | Good |
| Memory Usage | Low | Very Low | Moderate |
| Native AOT Support | Yes | Excellent | Limited |
| Advanced JSON Features | Moderate | Moderate | Excellent |
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
| Mistake | Impact |
|---|
| Benchmarking Debug builds | Misleading results |
| Measuring only serialization | Incomplete evaluation |
| Ignoring memory allocations | Hidden performance costs |
| Using unrealistic test objects | Poor production relevance |
| Mixing serializers unnecessarily | Increased maintenance |
| Drawing conclusions from a single benchmark | Inaccurate 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:
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.