Создание экземпляров JsonSerializerOptions с помощью System.Text.Json

В этой статье объясняется, как избежать проблем с производительностью при использовании 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 миллисекунд. Разница станет еще больше, если увеличить число итераций.

Сериализатор выполняет этап прогрева во время первой сериализации каждого типа в графе объектов при передаче ему нового экземпляра параметров. Этот прогрев включает создание кэша метаданных, необходимых для сериализации. Метаданные содержат делегаты для методов получения свойств, методов задания свойств, аргументов конструктора, заданных атрибутов и т. д. Этот кэш метаданных хранится в экземпляре параметров. Тот же процесс прогрева и создания кэша относится к десериализации.

Размер кэша метаданных в экземпляре JsonSerializerOptions зависит от числа сериализуемых типов. Если в сериализатор передается большое число типов, например, динамически создаваемые типы, то размер кэша будет продолжать расти и может привести к появлению OutOfMemoryException.

Свойство JsonSerializerOptions.Default.

Если экземпляр, который необходимо использовать, является экземпляром JsonSerializerOptions по умолчанию (имеет все параметры по умолчанию и преобразователи по умолчанию), используйте JsonSerializerOptions.Default свойство, а не создание экземпляра параметров. Дополнительные сведения см. в разделе "Использование преобразователя системы по умолчанию".

Копирование JsonSerializerOptions

Существует конструктор JsonSerializerOptions , который позволяет создать новый экземпляр с теми же параметрами, что и существующий экземпляр, как показано в следующем примере:

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, который принимает существующий экземпляр, недоступен в .NET Core 3.1.

Стандартные параметры веб-приложений для JsonSerializerOptions

Ниже приведены параметры с различными значениями по умолчанию для веб-приложений:

Существует конструктор JsonSerializerOptions, который позволяет создать новый экземпляр с параметрами по умолчанию, которые ASP.NET Core использовать для веб-приложений, как показано в следующем примере:

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

Ниже приведены параметры с различными значениями по умолчанию для веб-приложений:

Конструктор JsonSerializerOptions, указывающий набор значений по умолчанию, недоступен в .NET Core 3.1.

См. также раздел