Stop using Newtonsoft.Json

Serhii Korol - Sep 2 - - Dev Community

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());
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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; }
}
Enter fullscreen mode Exit fullscreen mode

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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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; }
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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;
            }
        }
    }
Enter fullscreen mode Exit fullscreen mode

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.

100000

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.

Buy Me A Beer

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .