Creación de instancias de JsonSerializerOptions con System.Text.Json

En este artículo se explica cómo evitar problemas de rendimiento al usar JsonSerializerOptions. También se muestra cómo usar los constructores con parámetros disponibles.

Reutilización de instancias de JsonSerializerOptions

Si usa JsonSerializerOptions repetidas veces con las mismas opciones, no cree una instancia de JsonSerializerOptions cada vez que lo use. Reutilice la misma instancia para cada llamada. Esta instrucción se aplica al código que se escribe para convertidores personalizados y al llamar a JsonSerializer.Serialize o JsonSerializer.Deserialize. Es seguro usar la misma instancia en varios subprocesos. Las caché de metadatos de la instancia de opciones son seguras para subprocesos y la instancia es inmutable después de la primera serialización o deserialización.

En el código siguiente se muestra la penalización de rendimiento al usar nuevas instancias de opciones.

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

El código anterior serializa un objeto pequeño 100 000 veces mediante la misma instancia de opciones. A continuación, serializa el mismo objeto el mismo número de veces y crea una nueva instancia de opciones cada vez. Normalmente la diferencia en el tiempo de ejecución es de 190 milisegundos frente a 40 140. Esta será mayor cuantas más iteraciones se realicen.

El serializador pasa por una fase de preparación durante la primera serialización de cada tipo en el gráfico de objetos cuando se le pasa una nueva instancia de opciones. Esta preparación incluye la creación de una caché de metadatos necesaria para la serialización. Los metadatos incluyen delegados para captadores de propiedades, establecedores, argumentos de constructor, atributos especificados, etc. Esta caché de metadatos se almacena en la instancia de opciones. El mismo proceso de preparación y caché se aplica a la deserialización.

El tamaño de la memoria caché de metadatos en una instancia de JsonSerializerOptions depende del número de tipos que se van a serializar. Si pasa numerosos tipos (por ejemplo, tipos generados dinámicamente) al serializador, el tamaño de la caché seguirá creciendo y puede acabar produciendo una excepción OutOfMemoryException.

La propiedad JsonSerializerOptions.Default

Si la instancia de JsonSerializerOptions que necesita usar es la instancia predeterminada (tiene toda la configuración predeterminada y los convertidores predeterminados), use la propiedad JsonSerializerOptions.Default en lugar de crear una instancia de opciones. Para más información, vea Usar el convertidor de sistema predeterminado.

Copia de JsonSerializerOptions

Hay un constructor de JsonSerializerOption que le permite crear una nueva instancia de con las mismas opciones que una instancia existente, como se muestra en el ejemplo siguiente:

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"
'}

La caché de metadatos de la instancia existente JsonSerializerOptions no se copia en la nueva instancia. Por lo tanto, el uso de este constructor no es lo mismo que reutilizar una instancia existente de JsonSerializerOptions.

Valores predeterminados web para JsonSerializerOptions

Las opciones siguientes tienen valores predeterminados diferentes para las aplicaciones web:

En .NET 9 y versiones posteriores, puede usar el singleton JsonSerializerOptions.Web para serializar con las opciones predeterminadas que ASP.NET Core usa para las aplicaciones web. En versiones anteriores, llame al constructor JsonSerializerOptions para crear una nueva instancia con los valores predeterminados web, como se muestra en el ejemplo siguiente:

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