Como instanciar as instâncias de JsonSerializerOptions com System.Text.Json
Este artigo explica como evitar problemas de desempenho ao usar JsonSerializerOptions. Ele também mostra como usar os construtores parametrizados disponíveis.
Reutilizar instâncias de JsonSerializerOptions
Se você usar JsonSerializerOptions
repetidamente com as mesmas opções, não crie uma instância JsonSerializerOptions
sempre que a usar. Reutilize a mesma instância para cada chamada. Essa orientação se aplica ao código que você escreve para conversores personalizados e ao chamar JsonSerializer.Serialize ou JsonSerializer.Deserialize. É seguro usar a mesma instância em vários threads. Os caches de metadados na instância de opções são thread-safe, e a instância é imutável após a primeira serialização ou desserialização.
O código a seguir demonstra a penalidade de desempenho por usar novas instâncias de opções.
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
O código anterior serializa um objeto pequeno 100.000 vezes usando a mesma instância de opções. Em seguida, ele serializa o mesmo objeto o mesmo número de vezes e cria uma nova instância de opções a cada vez. Uma diferença típica de tempo de execução é 190 comparado com 40.140 milissegundos. A diferença será ainda maior se você aumentar o número de iterações.
O serializador passa por uma fase de aquecimento durante a primeira serialização de cada tipo no grafo de objeto quando uma nova instância de opções é passada para ele. Esse aquecimento inclui a criação de um cache de metadados necessário para a serialização. Os metadados incluem delegados para getters de propriedade, setters, argumentos de construtor, atributos especificados e assim por diante. Esse cache de metadados é armazenado na instância de opções. O mesmo processo de aquecimento e cache se aplicam à desserialização.
O tamanho do cache de metadados em uma instância JsonSerializerOptions
depende do número de tipos a serem serializados. Se você passar vários tipos, por exemplo, tipos gerados dinamicamente, para o serializador, o tamanho do cache continuará crescendo e poderá acabar causando um OutOfMemoryException
.
A propriedade JsonSerializerOptions.Default
Se a instância de JsonSerializerOptions
da qual você precisa usar for a instância padrão (tem todas as configurações padrão e os conversores padrão), use a propriedade JsonSerializerOptions.Default em vez de criar uma instância de opções. Para obter mais informações, consulte Usar conversor de sistema padrão.
Copiar JsonSerializerOptions
Há um construtor JsonSerializerOptions que permite criar uma nova instância com as mesmas opções de uma instância existente, conforme mostrado no exemplo a seguir:
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"
'}
O cache de metadados da instância existente JsonSerializerOptions
não é copiado para a nova instância. Portanto, usar esse construtor não é o mesmo que reutilizar uma instância existente de JsonSerializerOptions
.
Padrões da Web para JsonSerializerOptions
As seguintes opções têm padrões diferentes para aplicativos Web:
- PropertyNameCaseInsensitive =
true
- PropertyNamingPolicy = CamelCase
- NumberHandling = AllowReadingFromString
No .NET 9 e versões posteriores, você pode usar o singleton JsonSerializerOptions.Web para serializar com as opções padrão que o ASP.NET Core usa para aplicativos Web. Em versões anteriores, chame o construtor JsonSerializerOptions para criar uma nova instância com os padrões da Web, conforme mostrado no exemplo a seguir:
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