Optimizing JSON Serialization in .NET: Newtonsoft.Json vs. System.Text.Json
JavaScript Object Notation (JSON) is a popular data storage and transmitting format. Whether you are a front-end or back-end developer, you must be familiar with it. As a.NET developer, you don't need an introduction to this text-based data format. The two most prominent libraries are Newtonsoft.Json and System.Text.Json. Newtonsoft.Json has been the industry standard for years, but Microsoft introduced System.Text.Json as the default. In this post, I will compare and contrast these two libraries.

Before we dig into the post, a short word about what to choose and when. If starting a new project on a newer version of .NET, you should always go with System.Text.Json. This is the default option now. I've decided to still write this post to give an overview of the differences between the two packages. A lot of code is still running on Newtonsoft.Json. So, if your code is currently using Newtonsoft.Json, this should help you decide if and when to upgrade to System.Text.Json. Let's get started with a quick intro.
Newtonsoft.Json
Newtonsoft.Json is a high-performance go-to library from James Newton-King for JSON serialization and deserialization. With almost 6 billion downloads, it is the first choice of many .NET developers for JSON operations. Its popularity is due to its seamless integration, complex object graph handling, custom conversions, and numerous other features. The package enables JSON customization operations at a granular level. The library provides one production-level solution for all .NET applications.
System.Text.Json
System.Text.Json is a Microsoft library for JSON serialization and deserialization. It was introduced in .NET Core 3.0 as an optimized and resource-friendly default alternative to Newtonsoft.Json. With every new release of .NET, Microsoft keeps updating it to meet modern development requirements. Built on low-allocating memory serialization and deserialization, it offers a faster job comparable to its counterpart.
Now, returning to the same question. If and when should you switch your existing Newtonsoft.Json usage to Microsoft's library? Which one should you choose before starting a project? We will review both options and provide a clear picture to answer the above questions.
Provision
Both Newtonsoft.Json and System.Text.Json come in a NuGet package. Newer project templates typically include System.Text.Json, while older ones do not.
Customization
You can customize the C# property name in JSON serialization in Newtonsoft.Json using JsonProperty
.
using Newtonsoft.Json;
public class Employee
{
[JsonProperty("full_name")]
public string Name { get; set; }
[JsonProperty("years_old")]
public int Age { get; set; }
}
While in System.Text.Json, it is done with JsonPropertyName
using System.Text.Json.Serialization;
public class Employee
{
[JsonPropertyName("full_name")]
public string Name { get; set; }
[JsonPropertyName("years_old")]
public int Age { get; set; }
}
Handling complex object graphs (nested objects & lists)
In Newtonsoft.Json you can use the JsonConvert.SerializeObject
method to serialize an object to JSON:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
public class Company
{
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
class Program
{
static void Main()
{
var company = new Company
{
Name = "Adams co.",
Employees = new List<Employee>
{
new Employee { Name = "Brett", Age = 28},
new Employee { Name = "Warner", Age = 24}
}
};
string json = JsonConvert.SerializeObject(company, Formatting.Indented);
Console.WriteLine(json);
}
}
Where you with System.Text.Json uses the JsonSerializer.Serialize
method:
using System;
using System.Collections.Generic;
using System.Text.Json;
public class Company
{
public string Name { get; set; }
public List<Employee> Employees { get; set; }
}
class Program
{
static void Main()
{
var company = new Company
{
Name = "Adams Co.",
Employees = new List<Employee>
{
new Employee { Name = "Brett", Age = 19},
new Employee { Name = "Craig", Age = 26}
}
};
var options = new JsonSerializerOptions { WriteIndented = true };
string json = JsonSerializer.Serialize(company, options);
Console.WriteLine(json);
}
}
Both libraries serialize nested objects, but Newtonsoft.Json uses Formatting.Indented
, while System.Text.Json provides WriteIndented
.
Custom JSON Converters
Both frameworks allow different ways to customize how the JSON is generated. Let's start by defining a class to help illustrate this:
public class Employee
{
public string Name { get; set; }
public DateTime JoiningDate { get; set; }
}
With Newtonsoft.Json you provide custom settings using the JsonSerializerSettings
class:
string jsonNewtonsoft = JsonConvert.SerializeObject(
new Employee
{
Name = "Donald",
JoiningDate = DateTime.UtcNow
},
new JsonSerializerSettings
{
DateFormatString = "yyyy-MM-dd HH:mm:ss"
});
Console.WriteLine(jsonNewtonsoft);
Output

System.Text.Json does not provide customization in the same way. Instead, you specify the custom configuration in a converter class:
public class MyJsonDateConverter: System.Text.Json.Serialization.JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("yyyy-MM-dd HH:mm:ss"));
}
}
string jsonSystemText = JsonSerializer.Serialize(
new Employee
{
Name = "Donald",
JoiningDate = DateTime.UtcNow
},
new JsonSerializerOptions
{
Converters = { new MyJsonDateConverter() }
});
Console.WriteLine(jsonSystemText);
Output

Polymorphic Serialization
Polymorphic Serialization is the reservation of the actual runtime type of the objects while serializing and deserializing an object hierarchy. Let us specify new classes to help test this:
public class Animal
{
public string Name { get; set; }
}
public class Dog : Animal
{
public string Breed { get; set; }
}
Newtonsoft.Json supports polymorphic serialization natively with TypeNameHandling.All
.
using Newtonsoft.Json;
Animal pet = new Dog { Name = "Tom", Breed = "German Shephard" };
// Serialize with type metadata
string json = JsonConvert.SerializeObject(pet, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
Console.WriteLine(json);
// Deserialize back to the correct type
Animal deserializedPet = JsonConvert.DeserializeObject<Animal>(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
Console.WriteLine(deserializedPet.GetType().Name);
While System.Text.Json does not natively provide type reservation in polymorphism, it will serialize the base class, ignoring the derived classes.
Animal pet = new Dog { Name = "Tom", Breed = "German Shepherd" };
string json = JsonSerializer.Serialize(pet);
Console.WriteLine(json);
// Deserialize
Animal deserializedPet = JsonSerializer.Deserialize<Animal>(json);
Console.WriteLine(deserializedPet.GetType().Name);
Output

System.Text.Json lacks type preservation and outputs the wrong type name. Instead, you need to define a JsonConverter
, which sometimes adds complexity to the project.
Performance Benchmarking
We discussed a few differences in the functionalities between the two libraries. Many of them are interchangeable, but for some advanced features, Newtonsoft.Json edges out due to its 14-year evolution. Now, let's check the performance side comparison, which can be critical for your application.
Step 1: Install the BenchmarkDotNet package
dotnet add package BenchmarkDotNet
Step 2: Define classes
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public List<string> Hobbies { get; set; }
}
public class Company
{
public string Name { get; set; }
public List<Person> Employees { get; set; }
}
Step 3: Prepare datasets and benchmark code
using Newtonsoft.Json;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
public class JsonComplexBenchmark
{
private Company sampleCompany;
private string largeJson;
[Params(1000, 10000, 50000, 100000)] // Different dataset sizes
public int EmployeeCount;
[GlobalSetup]
public void Setup()
{
sampleCompany = new Company
{
Name = "TechCorp",
Employees = new List<Person>()
};
for (int i = 0; i < EmployeeCount; i++)
{
sampleCompany.Employees.Add(new Person.
{
Name = "Employee" + i,
Age = 25 + (i % 10),
Hobbies = new List<string> { "Surfing", "Traveling", "Photography" }
});
}
// Serialize once for deserialization tests
largeJson = JsonConvert.SerializeObject(sampleCompany);
}
[Benchmark]
public string Newtonsoft_Serialize() => JsonConvert.SerializeObject(sampleCompany);
[Benchmark]
public string SystemTextJson_Serialize() => JsonSerializer.Serialize(sampleCompany);
[Benchmark]
public Company Newtonsoft_Deserialize() => JsonConvert.DeserializeObject<Company>(largeJson);
[Benchmark]
public Company SystemTextJson_Deserialize() => JsonSerializer.Deserialize<Company>(largeJson);
}
class Program
{
static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<JsonComplexBenchmark>();
}
}
Step 4: Test and analyze results
As we know, we need to execute the project for benchmarks in the release:
dotnet run -c Release
Results
Method | EmployeeCount | Mean | Allocated |
---|---|---|---|
Newtonsoft_Serialize | 1000 | 1,126.2 us | 367.43 KB |
SystemTextJson_Serialize | 1000 | 772.6 us | 146.71 KB |
Newtonsoft_Deserialize | 1000 | 1,483.9 us | 331.38 KB |
SystemTextJson_Deserialize | 1000 | 1,014.1 us | 305.73 KB |
Newtonsoft_Serialize | 10000 | 13,322.4 us | 3600.7 KB |
SystemTextJson_Serialize | 10000 | 6,380.5 us | 1484.55 KB |
Newtonsoft_Deserialize | 10000 | 23,103.2 us | 3384.18 KB |
SystemTextJson_Deserialize | 10000 | 17,149.2 us | 3147.82 KB |
Newtonsoft_Serialize | 50000 | 62,017.8 us | 18160.2 KB |
SystemTextJson_Serialize | 50000 | 29,332.7 us | 7500.43 KB |
Newtonsoft_Deserialize | 50000 | 122,182.6 us | 16652.2 KB |
SystemTextJson_Deserialize | 50000 | 90,769.1 us | 15480.06 KB |
Newtonsoft_Serialize | 100000 | 121,582.6 us | 36368.93 KB |
SystemTextJson_Serialize | 100000 | 59,224.2 us | 15017.87 KB |
Newtonsoft_Deserialize | 100000 | 246,666.4 us | 33301.25 KB |
SystemTextJson_Deserialize | 100000 | 184,932.6 us | 30955.61 KB |
We examine serialization and deserialization with each of them over inputs of 1000, 10000, 50000, and 100000. As the dataset increases, we notice a trend: System.Text.Json outperforms Newtonsoft.Json in operations in both time and memory usage. This indicates that System.Text.Json's performance is optimized and resource-efficient for projects with larger datasets.
Conclusion
Newtonsoft.Json is a JSON serialization and deserialization library that has been the primary choice in many dotnet projects. It is the most downloaded NuGet package. However, Microsoft has introduced System.Text.Json as a default library. We examine their differences and usage with coding examples. We also benchmarked them using an increasing dataset. Newtonsoft.Json has a wider range of features and advanced serialization options, such as polymorphic serialization, which its counterpart needs to improve. For many advanced operations, Newtonsoft.Json has evolved to be seamless. However, in the final part, we saw that Microsoft's System.Text.Json is faster and more memory-efficient as it is intended to provide an optimized solution. Each .NET version gets updated, and new features are added.
elmah.io: Error logging and Uptime Monitoring for your web apps
This blog post is brought to you by elmah.io. elmah.io is error logging, uptime monitoring, deployment tracking, and service heartbeats for your .NET and JavaScript applications. Stop relying on your users to notify you when something is wrong or dig through hundreds of megabytes of log files spread across servers. With elmah.io, we store all of your log messages, notify you through popular channels like email, Slack, and Microsoft Teams, and help you fix errors fast.
See how we can help you monitor your website for crashes Monitor your website