Improving Performance by Reusing JsonSerializerOptions in C#
By FoxLearn 2/4/2025 3:51:41 AM 71
However, there is a common performance pitfall when serializing objects: creating a new JsonSerializerOptions object every time. This practice can negatively affect performance, especially when serializing multiple objects of the same type. Fortunately, reusing JsonSerializerOptions can drastically improve performance by caching type information, reducing overhead.
Why Reuse JsonSerializerOptions?
The primary advantage of reusing a JsonSerializerOptions
object is the caching of type metadata. When you serialize an object for the first time, the type information has to be generated, which can be time-consuming. However, when you reuse the same JsonSerializerOptions
, this type information is cached, allowing the serializer to avoid unnecessary reprocessing in subsequent serializations.
By reusing JsonSerializerOptions
, we can see a substantial reduction in the time it takes to serialize the same types multiple times.
Performance Test Setup
To demonstrate the difference in performance, we will compare two approaches:
- Approach 1: Creating a new
JsonSerializerOptions
object for each serialization operation. - Approach 2: Reusing the same
JsonSerializerOptions
object for all serialization operations.
We will serialize 100 objects and compare the average serialization times, excluding the first run (which includes initialization overhead).
For this example, we’ll use a sample Product
object, which contains information like product name, category, price, and related reviews.
Approach 1: Creating a New JsonSerializerOptions Each Time
In this approach, we create a new JsonSerializerOptions
object each time we serialize a Product
object. This forces the serializer to reprocess the type metadata for every single object, resulting in higher overhead.
using System.Text.Json; using System.Diagnostics; var products = new List<Product> { new Product { Name = "Laptop", Category = "Electronics", Price = 999.99, Reviews = 150 }, new Product { Name = "Smartphone", Category = "Electronics", Price = 799.99, Reviews = 300 }, // Add more products as needed }; 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(products, options); sw.Stop(); nonCachingOptionTimes.Add(sw.Elapsed.TotalMilliseconds); } Console.WriteLine($"No caching - new 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()}");
Performance Results:
Creating New Options:
- Min:
0.004 ms
- Max:
2.315 ms
- Avg:
0.022 ms
- Min:
Serializing:
- First:
47.204 ms
- Min:
3.129 ms
- Max:
47.227 ms
- Avg:
4.723 ms
- Avg without First:
4.210 ms
- First:
Total time (creating new options + serializing, average without first): 4.232 ms
Approach 2: Reusing JsonSerializerOptions
In this approach, we create a single JsonSerializerOptions
object and reuse it for all 100 serialization operations. This avoids the overhead of creating new objects and regenerating type metadata, leading to faster serializations.
using System.Text.Json; using System.Diagnostics; 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(products, 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()}");
Performance Results:
- Serializing:
First:
50.002 ms
Min:
0.015 ms
Max:
50.015 ms
Avg:
0.557 ms
Avg without First:
0.0145 ms
Performance Comparison
Approach | Average Serialization Time (ms) |
---|---|
Not Reusing JsonSerializerOptions | 4.232 ms |
Reusing JsonSerializerOptions | 0.0145 ms |
Reusing JsonSerializerOptions
results in a massive performance improvement. In our example, reusing the same JsonSerializerOptions
object made serialization over 290x faster than creating a new one for every operation. The majority of the speedup comes from the caching of type metadata, which avoids repeated processing during serialization.
- Using the OrderBy and OrderByDescending in LINQ
- Querying with LINQ
- Optimizing Performance with Compiled Queries in LINQ
- MinBy() and MaxBy() Extension Methods in .NET
- SortBy, FilterBy, and CombineBy in NET 9
- Exploring Hybrid Caching in .NET 9.0
- Using Entity Framework with IDbContext in .NET 9.0
- Primitive types in C#