Jak utworzyć wystąpienie wystąpień JsonSerializerOptions za pomocą polecenia System.Text.Json

W tym artykule wyjaśniono, jak uniknąć problemów z wydajnością podczas korzystania z programu JsonSerializerOptions. Pokazuje również, jak używać konstruktorów sparametryzowanych, które są dostępne.

Ponowne używanie wystąpień JsonSerializerOptions

Jeśli używasz JsonSerializerOptions wielokrotnie z tymi samymi opcjami, nie twórz nowego JsonSerializerOptions wystąpienia za każdym razem, gdy go używasz. Ponownie użyj tego samego wystąpienia dla każdego wywołania. Te wskazówki dotyczą kodu pisanego dla konwerterów niestandardowych oraz podczas wywoływania JsonSerializer.Serialize lub JsonSerializer.Deserialize. Można bezpiecznie używać tego samego wystąpienia w wielu wątkach. Pamięci podręczne metadanych w wystąpieniu opcji są bezpieczne wątkowo, a wystąpienie jest niezmienne po pierwszej serializacji lub deserializacji.

Poniższy kod przedstawia karę za wydajność użycia nowych wystąpień opcji.

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

Powyższy kod serializuje mały obiekt 100 000 razy przy użyciu tego samego wystąpienia opcji. Następnie serializuje ten sam obiekt o tej samej liczbie razy i tworzy nowe wystąpienie opcji za każdym razem. Typowa różnica czasu wykonywania wynosi 190 w porównaniu z 40 140 milisekundami. Różnica jest jeszcze większa, jeśli zwiększysz liczbę iteracji.

Serializator przechodzi fazę rozgrzewania podczas pierwszej serializacji każdego typu na grafie obiektów, gdy do niego zostanie przekazane nowe wystąpienie opcji. Ta rozgrzewka obejmuje tworzenie pamięci podręcznej metadanych potrzebnych do serializacji. Metadane obejmują delegaty do metod pobierania właściwości, zestawów, argumentów konstruktora, określonych atrybutów itd. Ta pamięć podręczna metadanych jest przechowywana w wystąpieniu opcji. Ten sam proces rozgrzewania i pamięci podręcznej ma zastosowanie do deserializacji.

Rozmiar pamięci podręcznej metadanych w JsonSerializerOptions wystąpieniu zależy od liczby typów do serializacji. Jeśli przekażesz wiele typów — na przykład dynamicznie generowanych typów — do serializatora, rozmiar pamięci podręcznej będzie nadal rosnąć i może spowodować wystąpienie elementu OutOfMemoryException.

Właściwość JsonSerializerOptions.Default

Jeśli wystąpienie JsonSerializerOptions , którego chcesz użyć, jest wystąpieniem domyślnym (ma wszystkie ustawienia domyślne i konwertery domyślne), użyj JsonSerializerOptions.Default właściwości zamiast tworzenia wystąpienia opcji. Aby uzyskać więcej informacji, zobacz Use default system converter (Używanie domyślnego konwertera systemu).

Kopiowanie plików JsonSerializerOptions

Istnieje konstruktor JsonSerializerOptions, który umożliwia utworzenie nowego wystąpienia z tymi samymi opcjami co istniejące wystąpienie, jak pokazano w poniższym przykładzie:

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

Pamięć podręczna metadanych istniejącego JsonSerializerOptions wystąpienia nie jest kopiowana do nowego wystąpienia. Dlatego użycie tego konstruktora nie jest takie samo, jak ponowne użycie istniejącego wystąpienia klasy JsonSerializerOptions.

Wartości domyślne sieci Web dla JsonSerializerOptions

Następujące opcje mają różne wartości domyślne dla aplikacji internetowych:

W programie .NET 9 i nowszych wersjach można użyć pojedynczego JsonSerializerOptions.Web pliku do serializacji z opcjami domyślnymi, które ASP.NET Core używa dla aplikacji internetowych. We wcześniejszych wersjach wywołaj konstruktor JsonSerializerOptions, aby utworzyć nowe wystąpienie z wartościami domyślnymi sieci Web, jak pokazano w poniższym przykładzie:

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