Instanziieren von JsonSerializerOptions-Instanzen mit System.Text.Json

In diesem Artikel wird erläutert, wie Sie Leistungsprobleme vermeiden, die bei der Verwendung von JsonSerializerOptions auftreten können. Zudem wird die Verwendung der vorhandenen parametrisierten Konstruktoren veranschaulicht.

Wiederverwenden von JsonSerializerOptions-Instanzen

Wenn Sie JsonSerializerOptions wiederholt mit den gleichen Optionen verwenden, sollten Sie nicht bei jeder Verwendung eine neue JsonSerializerOptions-Instanz erstellen. Verwenden Sie für jeden Aufruf dieselbe Instanz. Dieser Leitfaden gilt für Code, den Sie für benutzerdefinierte Konverter und Aufrufe von JsonSerializer.Serialize oder JsonSerializer.Deserialize schreiben. Es ist sicher, dieselbe Instanz in mehreren Thread zu verwenden. Die Metadatencaches auf der Optionsinstanz sind threadsicher, und die Instanz ist nach der ersten Serialisierung oder Deserialisierung unveränderlich.

Der folgende Code veranschaulicht die Leistungseinbußen bei der Verwendung neuer JsonSerializerOptions-Instanzen.

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

Der vorangehende Code serialisiert ein kleines Objekt 100.000-mal mit derselben JsonSerializerOptions-Instanz. Anschließend wird dasselbe Objekt noch einmal genauso oft serialisiert, doch jedes Mal wird eine neue JsonSerializerOptions-Instanz erstellt. Der Laufzeitunterschied liegt bei 190 Millisekunden im Vergleich zu 40.140 Millisekunden. Wenn Sie mehr Iterationen durchführen, wird der Unterschied sogar noch größer.

Das Serialisierungsmodul durchläuft während der ersten Serialisierung jedes Typs im Objektgraph eine Aufwärmphase, wenn eine neue JsonSerializerOptions-Instanz übergeben wird. Diese Aufwärmphase beinhaltet eine Zwischenspeicherung der Metadaten, die für die Serialisierung benötigt werden. Diese Metadaten enthalten beispielsweise Delegaten für Eigenschaftengetter und -setter, Konstruktorargumente oder angegebene Attribute. Dieser Metadatencache wird in der JsonSerializerOptions-Instanz gespeichert. Die Aufwärmphase und die Cacheerstellung erfolgen auch bei der Deserialisierung.

Die Größe des Metadatencaches in einer JsonSerializerOptions-Instanz hängt von der Anzahl der zu serialisierenden Typen ab. Wenn Sie viele verschiedene Typen (z. B. dynamisch generierte Typen) an das Serialisierungsmodul übergeben, nimmt die Cachegröße weiter zu, und eine OutOfMemoryException-Ausnahme kann auftreten.

Die JsonSerializerOptions.Default-Eigenschaft.

Wenn die Instanz von JsonSerializerOptions, die Sie verwenden müssen, die Standardinstanz ist (also über alle Standardeinstellungen und die Standardkonverter verfügt), verwenden Sie die JsonSerializerOptions.Default-Eigenschaft, anstatt eine Optionsinstanz zu erstellen. Weitere Informationen finden Sie unter Verwenden des Standardsystemkonverters.

Kopieren von JsonSerializerOptions

Es gibt einen JsonSerializerOptions-Konstruktor, mit dem Sie eine neue Instanz mit den Optionen einer vorhandenen Instanz erstellen können, wie im folgenden Beispiel gezeigt:

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

Der Metadatencache der vorhandenen JsonSerializerOptions-Instanz wird nicht in die neue Instanz kopiert. Daher ist die Verwendung dieses Konstruktors nicht vergleichbar mit der Wiederverwendung einer vorhandenen Instanz von JsonSerializerOptions.

Webstandardwerte für JsonSerializerOptions

Die folgenden Optionen weisen unterschiedliche Standardwerte für Web-Apps auf:

In .NET 9 und höheren Versionen können Sie das JsonSerializerOptions.Web-Singleton für die Serialisierung mit den Standardoptionen verwenden, die ASP.NET Core für Web-Apps verwendet. Rufen Sie in früheren Versionen den JsonSerializerOptions-Konstruktor auf, um eine neue Instanz mit den Webstandardeinstellungen zu erstellen, wie im folgenden Beispiel gezeigt:

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