System.Text.Json을 사용하여 JsonSerializerOptions 인스턴스를 인스턴스화하는 방법

이 문서에서는 JsonSerializerOptions를 사용하는 경우 성능 문제를 방지하는 방법을 설명합니다. 또한 사용할 수 있는 매개 변수가 있는 생성자를 사용하는 방법을 보여 줍니다.

JsonSerializerOptions 인스턴스 다시 사용

동일한 옵션으로 JsonSerializerOptions를 반복적으로 사용하는 경우 사용할 때마다 새 JsonSerializerOptions 인스턴스를 만들지 마세요. 모든 호출에 대해 동일한 인스턴스를 다시 사용하세요. 이 지침은 사용자 지정 변환기에 대해 작성하는 코드와 JsonSerializer.Serialize 또는 JsonSerializer.Deserialize을 호출할 때 적용됩니다. 여러 스레드에서 동일한 인스턴스를 사용하는 것이 안전합니다. 옵션 인스턴스의 메타데이터 캐시는 스레드로부터 안전하며, 첫 번째 serialization 또는 deserialization 후에는 인스턴스를 변경할 수 없습니다.

다음 코드에서는 새 옵션 인스턴스를 사용하는 경우의 성능 저하를 보여 줍니다.

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입니다. 반복 횟수를 늘리면 차이가 훨씬 커집니다.

직렬 변환기는 새 옵션 인스턴스가 전달될 때 개체 그래프의 각 형식의 첫 번째 serialization 동안 준비 단계를 수행합니다. 이 준비에는 serialization에 필요한 메타데이터의 캐시를 만드는 작업이 포함됩니다. 메타데이터에는 getter 및 setter 속성, 생성자 인수, 지정된 특성 등에 대한 대리자가 포함됩니다. 이 메타데이터 캐시는 옵션 인스턴스에 저장됩니다. 동일한 준비 프로세스 및 캐시가 deserialization에 적용됩니다.

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 9 이상 버전에서는 JsonSerializerOptions.Web 싱글톤을 사용하여 웹앱에 ASP.NET Core가 사용하는 기본 옵션으로 직렬화할 수 있습니다. 이전 버전에서는 다음 예제와 같이 JsonSerializerOptions 생성자를 호출하여 웹 기본값으로 새 인스턴스를 만듭니다.

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