如何使用 System.Text.Json 实例化 JsonSerializerOptions 实例
本文介绍如何避免在使用 JsonSerializerOptions 时出现性能问题。 还介绍了如何使用可用的参数化构造函数。
重用 JsonSerializerOptions 实例
如果你通过相同的选项重复使用 JsonSerializerOptions
,则请勿在每次使用时都创建新的 JsonSerializerOptions
实例。 对每个调用重复使用同一实例。 本指南适用于你为自定义转换器编写的代码,以及调用 JsonSerializer.Serialize 或 JsonSerializer.Deserialize时。 在多个线程中使用相同的实例是安全的。 选项实例上的元数据缓存是线程安全的,并且实例在第一次序列化或反序列化之后不可变。
以下代码演示使用新的选项实例所带来的性能损失。
using System.Diagnostics;
using System.Text.Json;
namespace OptionsPerfDemo
{
public record Forecast(DateTime Date, int TemperatureC, string Summary);
public class Program
{
public static void Main()
{
Forecast forecast = new(DateTime.Now, 40, "Hot");
JsonSerializerOptions options = new() { WriteIndented = true };
int iterations = 100000;
var watch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
Serialize(forecast, options);
}
watch.Stop();
Console.WriteLine($"Elapsed time using one options instance: {watch.ElapsedMilliseconds}");
watch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
Serialize(forecast);
}
watch.Stop();
Console.WriteLine($"Elapsed time creating new options instances: {watch.ElapsedMilliseconds}");
}
private static void Serialize(Forecast forecast, JsonSerializerOptions? options = null)
{
_ = JsonSerializer.Serialize<Forecast>(
forecast,
options ?? new JsonSerializerOptions() { WriteIndented = true });
}
}
}
// Produces output like the following example:
//
//Elapsed time using one options instance: 190
//Elapsed time creating new options instances: 40140
前面的代码使用相同的选项实例对一个小型对象进行了 100,000 次序列化。 然后,它对相同的对象进行了相同次数的序列化,且每次都创建一个新的选项实例。 典型运行时的差值为 190 毫秒,而不是 40,140 毫秒。 如果增加迭代的次数,差值甚至越大。
当向序列化程序传递新的选项实例时,在对象图中每种类型的第一次序列化期间,序列化程序将执行一个预热阶段。 此预热包括创建序列化所需的元数据的缓存。 元数据包括对属性 Getter 的委托、Setter、构造函数参数、指定特性等。 此元数据缓存存储在选项实例中。 相同的预热过程和缓存适用于反序列化。
JsonSerializerOptions
实例中元数据缓存的大小取决于要序列化的类型的数量。 如果向序列化程序传递多种类型(例如动态生成的类型),缓存大小将继续增长,最终可导致 OutOfMemoryException
。
JsonSerializerOptions.Default
属性
如果需要使用的 JsonSerializerOptions
的实例为默认实例(具有所有默认设置和默认转换器),请使用 JsonSerializerOptions.Default 属性,而不是创建一个实例选项。 有关详细信息,请参阅使用默认系统转换器。
复制 JsonSerializerOptions
存在 JsonSerializerOptions constructor,可让你使用与现有实例相同的选项来创建新的实例,如以下示例中所示:
using System.Text.Json;
namespace CopyOptions
{
public class Forecast
{
public DateTime Date { get; init; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
};
public class Program
{
public static void Main()
{
Forecast forecast = new()
{
Date = DateTime.Now,
TemperatureC = 40,
Summary = "Hot"
};
JsonSerializerOptions options = new()
{
WriteIndented = true
};
JsonSerializerOptions optionsCopy = new(options);
string forecastJson =
JsonSerializer.Serialize<Forecast>(forecast, optionsCopy);
Console.WriteLine($"Output JSON:\n{forecastJson}");
}
}
}
// Produces output like the following example:
//
//Output JSON:
//{
// "Date": "2020-10-21T15:40:06.8998502-07:00",
// "TemperatureC": 40,
// "Summary": "Hot"
//}
Imports System.Text.Json
Imports System.Text.Json.Serialization
Namespace CopyOptions
Public Class Forecast
Public Property [Date] As Date
Public Property TemperatureC As Integer
Public Property Summary As String
End Class
Public NotInheritable Class Program
Public Shared Sub Main()
Dim forecast1 As New Forecast() With {
.[Date] = Date.Now,
.Summary = Nothing,
.TemperatureC = CType(Nothing, Integer)
}
Dim options As New JsonSerializerOptions() With {
.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
}
Dim optionsCopy As New JsonSerializerOptions
Dim forecastJson As String = JsonSerializer.Serialize(forecast1, optionsCopy)
Console.WriteLine($"Output JSON:{forecastJson}")
End Sub
End Class
End Namespace
' Produces output like the following example:
'
'Output JSON:
'{
' "Date": "2020-10-21T15:40:06.8998502-07:00",
' "TemperatureC": 40,
' "Summary": "Hot"
'}
现有 JsonSerializerOptions
实例的元数据缓存不会复制到新实例。 因此,使用此构造函数与重用现有实例 JsonSerializerOptions
不同。
JsonSerializerOptions 的 Web 默认值
对于 Web 应用,以下选项具有不同的默认值:
- PropertyNameCaseInsensitive =
true
- PropertyNamingPolicy = CamelCase
- NumberHandling = AllowReadingFromString
在 .NET 9 及更高版本中,可使用 JsonSerializerOptions.Web 单一实例通过 ASP.NET Core 用于 Web 应用的默认选项进行序列化。 在更低的版本中,请调用 JsonSerializerOptions 构造函数来使用 Web 默认值创建新实例,如以下示例所示:
using System.Text.Json;
namespace OptionsDefaults
{
public class Forecast
{
public DateTime? Date { get; init; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
};
public class Program
{
public static void Main()
{
Forecast forecast = new()
{
Date = DateTime.Now,
TemperatureC = 40,
Summary = "Hot"
};
JsonSerializerOptions options = new(JsonSerializerDefaults.Web)
{
WriteIndented = true
};
Console.WriteLine(
$"PropertyNameCaseInsensitive: {options.PropertyNameCaseInsensitive}");
Console.WriteLine(
$"JsonNamingPolicy: {options.PropertyNamingPolicy}");
Console.WriteLine(
$"NumberHandling: {options.NumberHandling}");
string forecastJson = JsonSerializer.Serialize<Forecast>(forecast, options);
Console.WriteLine($"Output JSON:\n{forecastJson}");
Forecast? forecastDeserialized =
JsonSerializer.Deserialize<Forecast>(forecastJson, options);
Console.WriteLine($"Date: {forecastDeserialized?.Date}");
Console.WriteLine($"TemperatureC: {forecastDeserialized?.TemperatureC}");
Console.WriteLine($"Summary: {forecastDeserialized?.Summary}");
}
}
}
// Produces output like the following example:
//
//PropertyNameCaseInsensitive: True
//JsonNamingPolicy: System.Text.Json.JsonCamelCaseNamingPolicy
//NumberHandling: AllowReadingFromString
//Output JSON:
//{
// "date": "2020-10-21T15:40:06.9040831-07:00",
// "temperatureC": 40,
// "summary": "Hot"
//}
//Date: 10 / 21 / 2020 3:40:06 PM
//TemperatureC: 40
//Summary: Hot
Imports System.Text.Json
Namespace OptionsDefaults
Public Class Forecast
Public Property [Date] As Date
Public Property TemperatureC As Integer
Public Property Summary As String
End Class
Public NotInheritable Class Program
Public Shared Sub Main()
Dim forecast1 As New Forecast() With {
.[Date] = Date.Now,
.TemperatureC = 40,
.Summary = "Hot"
}
Dim options As New JsonSerializerOptions(JsonSerializerDefaults.Web) With {
.WriteIndented = True
}
Console.WriteLine(
$"PropertyNameCaseInsensitive: {options.PropertyNameCaseInsensitive}")
Console.WriteLine(
$"JsonNamingPolicy: {options.PropertyNamingPolicy}")
Console.WriteLine(
$"NumberHandling: {options.NumberHandling}")
Dim forecastJson As String = JsonSerializer.Serialize(forecast1, options)
Console.WriteLine($"Output JSON:{forecastJson}")
Dim forecastDeserialized As Forecast = JsonSerializer.Deserialize(Of Forecast)(forecastJson, options)
Console.WriteLine($"Date: {forecastDeserialized.[Date]}")
Console.WriteLine($"TemperatureC: {forecastDeserialized.TemperatureC}")
Console.WriteLine($"Summary: {forecastDeserialized.Summary}")
End Sub
End Class
End Namespace
' Produces output like the following example:
'
'PropertyNameCaseInsensitive: True
'JsonNamingPolicy: System.Text.Json.JsonCamelCaseNamingPolicy
'NumberHandling: AllowReadingFromString
'Output JSON:
'{
' "date": "2020-10-21T15:40:06.9040831-07:00",
' "temperatureC": 40,
' "summary": "Hot"
'}
'Date: 10 / 21 / 2020 3:40:06 PM
'TemperatureC: 40
'Summary: Hot