Comment instancier des instances JsonSerializerOptions avec System.Text.Json

Cet article explique comment éviter les problèmes de performances lorsque vous utilisez JsonSerializerOptions. Il montre également comment utiliser les constructeurs paramétrisés disponibles.

Réutiliser des instances JsonSerializerOptions

Si vous utilisez JsonSerializerOptions plusieurs fois avec les mêmes options, ne créez pas d’instance de JsonSerializerOptions chaque fois que vous l’utilisez. Réutilisez la même instance pour chaque appel. Ces conseils s’appliquent au code que vous écrivez pour les convertisseurs personnalisés et lorsque vous appelez JsonSerializer.Serialize ou JsonSerializer.Deserialize. Il est sûr d’utiliser la même instance sur plusieurs threads. Les caches de métadonnées sur l’instance d’options sont thread-safe, et l’instance est immuable après la première sérialisation ou désérialisation.

Le code suivant illustre la pénalité de performances pour l’utilisation de nouvelles instances d’options.

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

Le code précédent sérialise un petit objet 100 000 fois à l’aide de la même instance d’options. Ensuite, il sérialise le même objet le même nombre de fois et crée une instance d’options à chaque fois. Une différence de temps d’exécution classique est de 190 par rapport à 40 140 millisecondes. La différence est encore plus grande si vous augmentez le nombre d’itérations.

Le sérialiseur subit une phase de préchauffage lors de la première sérialisation de chaque type dans le graphique d’objets lorsqu’une nouvelle instance d’options lui est passée. Ce préchauffage comprend la création d’un cache de métadonnées nécessaire à la sérialisation. Les métadonnées incluent des délégués aux getters de propriété, aux setters, aux arguments de constructeur, aux attributs spécifiés, etc. Ce cache de métadonnées est stocké dans l’instance d’options. Le même processus de préchauffage et le même cache s’appliquent à la désérialisation.

La taille du cache de métadonnées dans une instance JsonSerializerOptions dépend du nombre de types à sérialiser. Si vous passez de nombreux types (par exemple, des types générés dynamiquement) au sérialiseur, la taille du cache continuera à croître et peut finir par provoquer un OutOfMemoryException.

La propriété JsonSerializerOptions.Default

Si l’instance de JsonSerializerOptions que vous devez utiliser est l’instance par défaut (possède tous les paramètres par défaut et les convertisseurs par défaut), utilisez la propriété JsonSerializerOptions.Default au lieu de créer une instance d’options. Pour plus d’informations, consultez Utiliser le convertisseur système par défaut.

Copier JsonSerializerOptions

Il existe un constructeur JsonSerializerOptions qui vous permet de créer une instance avec les mêmes options qu’une instance existante, comme illustré dans l’exemple suivant :

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

Le cache de métadonnées de l’instance JsonSerializerOptions existante n’est pas copié dans la nouvelle instance. Par conséquent, l’utilisation de ce constructeur n’est pas la même chose que la réutilisation d’une instance existante de JsonSerializerOptions.

Valeurs web par défaut pour JsonSerializerOptions

Les options suivantes ont des valeurs par défaut différentes pour les applications web :

Dans .NET 9 et versions ultérieures, vous pouvez utiliser le singleton JsonSerializerOptions.Web pour sérialiser avec les options par défaut utilisées par ASP.NET Core pour les applications web. Dans les versions antérieures, appelez le constructeur JsonSerializerOptions pour créer une instance avec les valeurs par défaut web, comme illustré dans l’exemple suivant :

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