Hello folks. In this article, I would like to illustrate why you no longer need to use the Newtonsoft.Json package to work with JSON.
Why don't I advise using Newtonsoft? The last release was on 8th March 2023. This project isn't developing and optimizing.
Now, there are three approaches to serializing and deserializing JSON.
I'll describe these approaches with code examples, make benchmarks, and show the performance difference. I'll also be using the latest version of .NET9
.
Preconditions
It would help to have a base knowledge of C# and .NET
. You should also install the BenchmarkDotNet
and Newtonsoft.Json
packages. And, of course, the .NET9
should also be installed.
Building base project
First, create a simple console application from the template and install the abovementioned packages.
Let's code. In a Program.cs
file, add this code:
internal static class Program
{
private static void Main()
{
BenchmarkRunner.Run<JsonPerformance>(new BenchmarkConfig());
}
}
This needed to run the benchmark. Further, let's add configuration for the benchmark.
public class BenchmarkConfig : ManualConfig
{
public BenchmarkConfig()
{
AddJob(Job.ShortRun
.WithRuntime(CoreRuntime.Core90)
.WithJit(Jit.Default)
.WithPlatform(Platform.X64)
);
AddLogger(ConsoleLogger.Default);
AddColumnProvider(BenchmarkDotNet.Columns.DefaultColumnProviders.Instance);
AddExporter(BenchmarkDotNet.Exporters.HtmlExporter.Default);
}
}
In this code, I have added the runtime, JIT, and platform. If, for some reason, you are unable to install .NET9
, you can use. .NET8
. Let's look at the methods below. AddLogger
is needed for the logging to the console. It is needed if you want to see the results of the performance tests in the console. AddColumnProvider
is needed to generate the results in the table. AddExporter
needs to export to the format you are interested in. I have chosen the HTML format. You can choose, for example, the Markdown format.
Next, let's create a simple model that we serialize to JSON.
public record Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
public double YearsExperience { get; set; }
public List<string> Languages { get; set; }
public bool OpenToWork { get; set; }
}
Next step, we need to seed data.
public static class Generator
{
public static List<Person> GeneratePersons(int count)
{
var persons = new List<Person>();
for (int i = 0; i < count; i++)
{
persons.Add(GeneratePerson());
}
return persons;
}
private static Person GeneratePerson()
{
var random = new Random();
var randomNumber = random.Next(1, 100);
var person = new Person
{
FirstName = Guid.NewGuid().ToString(),
LastName = Guid.NewGuid().ToString(),
Age = randomNumber,
Languages = ["English", "French", "Spanish"],
YearsExperience = random.NextDouble(),
OpenToWork = randomNumber % 2 == 0
};
return person;
}
}
Building write and read JSON methods
Before that, let's create a new class, such as JsonPerformance
. It will contain our methods. Into this class, put these properties that will keep serialized and deserialized data:
private List<Person> Persons { get; } = Generator.GeneratePersons(100000);
private string? Json { get; set; }
Let's start with the realization of Newtonsoft's methods. It's typical operations with which you are likely familiar.
[Benchmark]
public void NewtonJsonWrite()
{
Json = JsonConvert.SerializeObject(Persons);
}
[Benchmark]
public void NewtonJsonRead()
{
if (Json == null) return;
var persons = JsonConvert.DeserializeObject<List<Person>>(Json);
}
In the next step, let's add methods using tools built into the framework. This realization is similar to Newtonsoft's.
[Benchmark]
public void FrameworkJsonWrite()
{
Json = JsonSerializer.Serialize(Persons);
}
[Benchmark]
public void FrameworkJsonRead()
{
if (Json == null) return;
var persons = JsonSerializer.Deserialize<List<Person>>(Json);
}
Finally, the last method is implemented with the manual approach, where you need to read and write data manually.
[Benchmark]
public void ManualJsonWrite()
{
var builder = new StringBuilder();
var writer = new StringWriter(builder);
using (var textWriter = new JsonTextWriter(writer))
{
textWriter.WriteStartArray();
foreach (var person in Persons)
{
textWriter.WriteStartObject();
textWriter.WritePropertyName("FirstName");
textWriter.WriteValue(person.FirstName);
textWriter.WritePropertyName("LastName");
textWriter.WriteValue(person.LastName);
textWriter.WritePropertyName("Age");
textWriter.WriteValue(person.Age);
textWriter.WritePropertyName("YearsExperience");
textWriter.WriteValue(person.YearsExperience);
textWriter.WritePropertyName("Languages");
textWriter.WriteStartArray();
foreach (var language in person.Languages)
{
textWriter.WriteValue(language);
}
textWriter.WriteEndArray();
textWriter.WritePropertyName("OpenToWork");
textWriter.WriteValue(person.OpenToWork);
textWriter.WriteEndObject();
}
textWriter.WriteEndArray();
}
Json = builder.ToString();
}
[Benchmark]
public void ManualJsonRead()
{
if (Json == null) return;
var persons = new List<Person>();
using var reader = new StringReader(Json);
using var jsonReader = new JsonTextReader(reader);
jsonReader.Read();
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.StartObject)
{
var person = new Person();
while (jsonReader.Read())
{
if (jsonReader.TokenType == JsonToken.PropertyName)
{
var propertyName = jsonReader.Value?.ToString();
jsonReader.Read();
switch (propertyName)
{
case "FirstName":
person.FirstName = jsonReader.Value?.ToString() ?? string.Empty;
break;
case "LastName":
person.LastName = jsonReader.Value?.ToString() ?? string.Empty;
break;
case "Age":
person.Age = Convert.ToInt32(jsonReader.Value);
break;
case "YearsExperience":
person.YearsExperience = Convert.ToInt32(jsonReader.Value);
break;
case "Languages":
person.Languages = jsonReader.Value?.ToString()?.Split(',').ToList() ?? new List<string>();
break;
case "OpenToWork":
person.OpenToWork = Convert.ToBoolean(jsonReader.Value);
break;
}
}
else if (jsonReader.TokenType == JsonToken.EndObject)
{
break;
}
}
persons.Add(person);
}
else if (jsonReader.TokenType == JsonToken.EndArray)
{
break;
}
}
}
Performance
When we implement all methods, we can check performance. I'll take a series of benchmarks with large quantities of items, starting from 100000 items. Check this out. Before we start, you should switch your project to Release mode. Let's go.
As you can see, Newtonsoft performs lower than other methods. It is lowest in writing and faster in reading. The manual approach performs similarly to the system approach in writing but is lower in reading.
Conclusions
I recommend using a system tool to work with JSON. It is faster than other approaches and is a part of the framework that constantly develops and optimizes for the latest version of .NET
.
Thanks a lot for reading, and happy coding.
The source code is HERE.