C# – Reuse JsonSerializerOptions for performance

Reusing JsonSerializerOptions (from System.Text.Json) is optimal for performance. It caches type info, which results in a 200x speedup when it deals with the type again. Therefore, always try to reuse JsonSerializerOptions.

I’ll show a speed comparison of serializing with and without reusing JsonSerializerOptions.

Measuring the performance gains of reusing JsonSerializerOptions

To measure the performance gains of reusing JsonSerializerOptions, I compared serializing 100 objects with two approaches:

  • Approach 1 – Create a new JsonSerializerOptions object for each serialization operation.
  • Approach 2 – Reuse a single JsonSerializerOptions object for all 100 serialization operations.

It’s always slow when serializing an object for the first time (due to initialization overhead). That’s why I compared the average time without the first serialization.

Here are the performance test results:

ApproachAverage serialization time
Not reusing JsonSerializerOptions3.0651 ms
Reusing JsonSerializerOptions0.0145 ms

This means reusing JsonSerializerOptions makes serialization over 200x faster (3.0651 ms / 0.00145 ms).

Creating the JsonSerializerOptions object only accounts for about 0.02 ms on average. That’s nothing compared to the overall time. The major speedup is thanks to JsonSerializerOptions caching type info.

Next, I’ll show the code used for these two approaches.

Approach 1 – Using a new JsonSerializerOptions each time

Here’s the performance test code for approach 1. This loops over 100 objects and serializes them. It creates a new JsonSerializerOptions object each time.

using System.Text.Json;

List<double> nonCachingOptionTimes = new List<double>();
List<double> timeForCreatingNewOptions = new List<double>();
Stopwatch sw = new Stopwatch();

for (int i = 0; i < 100; i++)
{
	sw.Restart();
	var options = new JsonSerializerOptions() { WriteIndented = true };
	options.Converters.Add(new JsonStringEnumConverter());
	timeForCreatingNewOptions.Add(sw.Elapsed.TotalMilliseconds);
	
	sw.Restart();
	var json = JsonSerializer.Serialize(nflTeam, options);
	sw.Stop();
	nonCachingOptionTimes.Add(sw.Elapsed.TotalMilliseconds);
}

Console.WriteLine($"no caching - newing up options. min={timeForCreatingNewOptions.Min()} max={timeForCreatingNewOptions.Max()} avg={timeForCreatingNewOptions.Average()}");
Console.WriteLine($"no caching - serializing. first={nonCachingOptionTimes.First()} min={nonCachingOptionTimes.Min()} max={nonCachingOptionTimes.Max()} avg={nonCachingOptionTimes.Average()} avgWithoutFirst={nonCachingOptionTimes.Skip(1).Average()}");
Code language: C# (cs)

Note: This is capturing execution time with a Stopwatch, then adding the elapsed time to a list. At the end, it’s calculating the stats for the list of times by using Linq methods.

Creating the options object:

  • Min=0.0024 ms
  • Max=1.8253 ms
  • Avg=0.0221 ms

Serializing:

  • First=43.0357 ms
  • Min=2.4857 ms
  • Max=43.036 ms
  • Avg=3.4436 ms
  • AvgWithoutFirst=3.043 ms

Total (creating new options object + serializing) average without first: 0.0221 ms + 3.043 ms = 3.0651 ms

Approach 2 – Reusing JsonSerializerOptions

Here’s the performance test code for approach 2. This creates a single JsonSerializerOptions object and uses it while serializing 100 objects.

using System.Text.Json;

var cachedOption = new JsonSerializerOptions() { WriteIndented = true };
cachedOption.Converters.Add(new JsonStringEnumConverter());
List<double> cachedOptionTimes = new List<double>();
Stopwatch sw = new Stopwatch();

for (int i = 0; i < 100; i++)
{
	sw.Restart();
	var json = JsonSerializer.Serialize(nflTeam, cachedOption);
	sw.Stop();
	cachedOptionTimes.Add(sw.Elapsed.TotalMilliseconds);
}

Console.WriteLine($"caching. first={cachedOptionTimes.First()} min={cachedOptionTimes.Min()} max={cachedOptionTimes.Max()} avg={cachedOptionTimes.Average()}  avgWithoutFirst={cachedOptionTimes.Skip(1).Average()}");
Code language: C# (cs)

Note: Alternatively to measuring performance with Stopwatch, you can use Benchmark.NET. I often prefer the simpler (and much faster) approach with Stopwatch for good-enough comparisons.

Serializing:

  • First=45.39 ms
  • Min=0.0107 ms
  • Max = 45.39 ms
  • Avg = 0.4678 ms
  • Average without first = 0.0145 ms

For reference – The object used for performance tests

Here is the object I used in the performance tests.

{
	var team = new NFLTeam()
	{
		City = "Detroit",
		Name = "Lions",
		Conference = Conferences.NFC,
		Divison = Divisions.North,
		HeadCoach = new Person()
		{
			FirstName = "Matt",
			LastName = "Patricia"
		},
		Stats = new Stats()
		{
			RegularSeasonWins = 559,
			RegularSeasonLosses = 658,
			RegularSeasonTies = 32,
			PlayoffWins = 7,
			PlayoffLosses = 13,
			SuperBowlWins = 0,
			SuperBowlLosses = 0
		},
		Players = new List<Player>()
		{
			new Player()
			{
				FirstName = "Matthew",
				LastName = "Stafford",
				Position = PlayerPositions.QB,
				YearsOfExperience = 12,
				College = "Georgia"
			},
			new Player()
			{
				FirstName = "Kenny",
				LastName = "Golladay",
				Position = PlayerPositions.WR,
				YearsOfExperience = 4,
				College = "Northern Illinois"
			},
			new Player()
			{
				FirstName = "Tracy",
				LastName = "Walker",
				Position = PlayerPositions.DB,
				YearsOfExperience = 3,
				College = "Louisiana-Lafayette"
			},
			new Player()
			{
				FirstName = "T.J.",
				LastName = "Hockenson",
				Position = PlayerPositions.TE,
				YearsOfExperience = 2,
				College = "Iowa"
			}
		}
	};
	return team;
}
Code language: C# (cs)

Leave a Comment