Share via


如何使用 System.Text.Json 將 JsonSerializerOptions 執行個體具現化

此文章說明如何在使用 JsonSerializerOptions 時避免效能問題。 其中也會示範如何使用可用的參數化建構函式。

重複使用 JsonSerializerOptions 執行個體

如果您搭配相同選項重複使用 JsonSerializerOptions,請勿在每次使用它時建立新的 JsonSerializerOptions 執行個體。 針對每個呼叫重複使用相同的執行個體。 此指引適用於您為自訂轉換器撰寫的程式碼,以及您呼叫 JsonSerializer.SerializeJsonSerializer.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 次。 然後,將相同的物件序列化相同次數,並每次建立新的選項執行個體。 相較於 40,140 毫秒,典型的執行階段差異為 190 毫秒。 若您增加反覆運算次數,則差異甚至更大。

當新的選項執行個體傳遞至物件圖時,序列化程式會在物件圖中每個類型的第一次序列化期間進入準備階段。 此準備包括建立序列化所需的中繼資料快取。 中繼資料包含屬性 (Property) getter、setter、建構函式引數、指定屬性 (Attribute) 等委派。 此中繼資料快取會儲存於選項執行個體中。 相同的準備流程與快取適用於還原序列化。

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 的 Web 預設值

下列選項針對 Web 應用程式有不同的預設值:

在 .NET 9 和更新版本中,您可以使用 JsonSerializerOptions.Web 單一資料庫來序列化搭配 ASP.NET Core 用於 Web 應用程式的預設選項。 在舊版中,呼叫 JsonSerializerOptions 建構函式來建立具有 Web 預設值的新執行個體,如下列範例所示:

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