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 as a high-performance replacement for Newtonsoft.Json. You'll find many benchmarks online showing the performance gains when switching to System.Text.Json.

However, there are GitHub issues reporting that JsonSerializer can be less performant or lead to memory leaks when not used correctly. In this post, I'll explain why reusing the same JsonSerializerOptions instance whenever possible is essential to avoiding a performance penalty.

C#
// 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 significant in both memory usage and execution time. For 10,000 serializations, not caching is 1000 times slower and uses 193MB more memory. Note that instantiating 10,000 JsonSerializerOptions instances only requires 12 MB and 3 ms, so that alone does not account for the difference. To understand why, look at how JsonSerializer works. When you serialize a type, the serializer dynamically generates code for that type and the provided options. If new options are provided, it cannot reuse the cached code and must generate it again. This is why you should reuse the same options instance as much as possible when using JsonSerializer.

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

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

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

Note that .NET now contains a Roslyn analyzer that warns you when you create a new JsonSerializerOptions instance without caching it: CA1869: Cache and reuse 'JsonSerializerOptions' instances

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

Follow me:
Enjoy this blog?