Avoid performance issues with JsonSerializer by reusing the same JsonSerializerOptions instance

  • Gérald Barré

.NET Core 3.0 introduced a json serializer in the System.Text.Json namespace to replace Newtonsoft.Json in high performance scenario. You'll find many benchmark on other website showing the performance gains when switching to S.T.Json.

However, you can find some issue on GitHub that shows that the JsonSerializer is less performant or leads to memory leaks. This can be the case when not used as expected. In this post, I'll show you why it is important to reuse the JsonSerializerOptions instance whenever it's possible to avoid a performance penalty.

// The following code reuse the default options instance which is automatically cached
public void Serialize_DefaultOptions()
{
    for (var i = 0; i < 10_000; i++)
    {
        JsonSerializer.Serialize(_data);
    }
}

// The following code reuse the same options instance
public void Serialize_CachedOptions()
{
    var options = new JsonSerializerOptions { WriteIndented = false };
    for (var i = 0; i < 10_000; i++)
    {
        JsonSerializer.Serialize(_data, options);
    }
}

// ❌ Do not use the following code
// The following code doesn't reuse the options
public void Serialize_NewOptions()
{
    for (var i = 0; i < 10_000; i++)
    {
        JsonSerializer.Serialize(_data, new JsonSerializerOptions { WriteIndented = false });
    }
}

The difference between caching and not caching the options is huge in term of memory and execution time. For 10000 serialization, it is 1000 times slower and uses 193MB more memory. Note that instantiating 10000 JsonSerializerOptions only requires 12MB and 3ms, so it doesn't explains the huge memory and execution time difference. Instead you have to look at how the JsonSerializer works. When you serialize a new type, the serializer generates code dynamically for these type and the provided options. If you provide new options, it cannot reuse the previously generated code from its cache, so it'll generate a new one for the new options. This is why you should reuse as much as possible the options instances when using the JsonSerializer.

One way to reuse the same options instance and avoid performance penalty is to use a static field to store it:

static JsonSerializerOptions s_options = new JsonSerializerOptions { WriteIndented = false };

void SerializeToFile<T>(string path, T data)
{
    File.WriteAllText(path, JsonSerializer.Serialize(data, s_options));
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee