System.Text.Json을 사용하여 파생 클래스의 속성을 직렬화하는 방법

이 문서에서는 System.Text.Json 네임스페이스를 사용하여 파생 클래스의 속성을 직렬화하는 방법을 알아봅니다.

파생 클래스의 속성 직렬화

.NET 7 이전 버전에서는 System.Text.Json이 다형 형식 계층 구조의 직렬화를 지원하지 않습니다. 예를 들어 속성의 형식이 인터페이스 또는 추상 클래스로 정의된 경우에는 런타임 형식에 추가 속성이 있더라도 인터페이스 또는 추상 클래스에 정의된 속성만 직렬화됩니다. 이 동작의 예외는 이 섹션에 설명되어 있습니다. .NET 7에서의 지원에 대한 자세한 내용은 .NET 7의 다형 직렬화를 참조하세요.

예를 들어 WeatherForecast 클래스와 WeatherForecastDerived 파생 클래스가 있다고 가정해 봅시다.

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
Public Class WeatherForecast
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
End Class
public class WeatherForecastDerived : WeatherForecast
{
    public int WindSpeed { get; set; }
}
Public Class WeatherForecastDerived
    Inherits WeatherForecast
    Public Property WindSpeed As Integer
End Class

그리고 컴파일 시간에 Serialize 메서드의 형식 인수가 WeatherForecast라고 가정하겠습니다.

var options = new JsonSerializerOptions
{
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecast>(weatherForecast, options);
Dim options As JsonSerializerOptions = New JsonSerializerOptions With {
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecast1, options)

이 시나리오에서는 weatherForecast 개체가 WeatherForecastDerived 개체인 경우에도 WindSpeed 속성이 직렬화되지 않습니다. 기본 클래스 속성만 직렬화됩니다.

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot"
}

이 동작의 목적은 파생된 런타임 생성 형식에서 실수로 데이터가 노출되는 것을 방지하는 것입니다.

이전 예제의 파생 형식 속성을 직렬화하려면 다음 방법 중 하나를 사용합니다.

  • 런타임에 형식을 지정할 수 있는 Serialize 오버로드를 호출합니다.

    options = new JsonSerializerOptions
    {
        WriteIndented = true
    };
    jsonString = JsonSerializer.Serialize(weatherForecast, weatherForecast.GetType(), options);
    
    options = New JsonSerializerOptions With {
        .WriteIndented = True
    }
    jsonString = JsonSerializer.Serialize(weatherForecast1, weatherForecast1.[GetType](), options)
    
  • object로 직렬화할 개체를 선언합니다.

    options = new JsonSerializerOptions
    {
        WriteIndented = true
    };
    jsonString = JsonSerializer.Serialize<object>(weatherForecast, options);
    
    options = New JsonSerializerOptions With {
        .WriteIndented = True
    }
    jsonString = JsonSerializer.Serialize(Of Object)(weatherForecast1, options)
    

위의 예제 시나리오에서 두 방법 모두 다음과 같이 WindSpeed 속성이 JSON 출력에 포함됩니다.

{
  "WindSpeed": 35,
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot"
}

중요

이러한 접근 방식은 루트 개체의 속성이 아니라 직렬화할 루트 개체에 대해서만 다형 직렬화를 제공합니다.

object 형식으로 정의하면 하위 수준 개체에 대한 다형 직렬화를 얻을 수 있습니다. 예를 들어 WeatherForecast 클래스에 WeatherForecast 또는 object 형식으로 정의할 수 있는 PreviousForecast라는 속성이 있다고 가정하겠습니다.

public class WeatherForecastWithPrevious
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
    public WeatherForecast? PreviousForecast { get; set; }
}
Public Class WeatherForecastWithPrevious
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
    Public Property PreviousForecast As WeatherForecast
End Class
public class WeatherForecastWithPreviousAsObject
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
    public object? PreviousForecast { get; set; }
}
Public Class WeatherForecastWithPreviousAsObject
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
    Public Property PreviousForecast As Object
End Class

PreviousForecast 속성은 다음과 같이 WeatherForecastDerived 인스턴스를 포함합니다.

  • WeatherForecastWithPrevious 직렬화의 JSON 출력에는 WindSpeed가 포함되지 않습니다.
  • WeatherForecastWithPreviousAsObject 직렬화의 JSON 출력에는 WindSpeed가 포함됩니다.

루트 개체가 파생 형식이 아닐 수도 있으므로 WeatherForecastWithPreviousAsObject를 직렬화하기 위해 Serialize<object> 또는 GetType을 반드시 호출해야 하는 것은 아닙니다. 예를 들어 다음 코드 예제는 Serialize<object> 또는 GetType을 호출하지 않습니다.

options = new JsonSerializerOptions
{
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject, options);
options = New JsonSerializerOptions With {
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(weatherForecastWithPreviousAsObject1, options)

이전 코드는 다음과 같이 WeatherForecastWithPreviousAsObject를 올바르게 직렬화합니다.

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot",
  "PreviousForecast": {
    "WindSpeed": 35,
    "Date": "2019-08-01T00:00:00-07:00",
    "TemperatureCelsius": 25,
    "Summary": "Hot"
  }
}

속성을 object로 정의하는 동일한 방법이 인터페이스에서도 작동합니다. 다음과 같은 인터페이스 및 구현이 있고, 구현 인스턴스가 포함된 속성을 사용하여 클래스를 직렬화하려는 경우를 가정해 보겠습니다.

namespace SystemTextJsonSamples
{
    public interface IForecast
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
    }

    public class Forecast : IForecast
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
        public int WindSpeed { get; set; }
    }

    public class Forecasts
    {
        public IForecast? Monday { get; set; }
        public object? Tuesday { get; set; }
    }
}
Namespace SystemTextJsonSamples

    Public Interface IForecast
        Property [Date] As DateTimeOffset
        Property TemperatureCelsius As Integer
        Property Summary As String
    End Interface

    Public Class Forecast
        Implements IForecast
        Public Property [Date] As DateTimeOffset Implements IForecast.[Date]
        Public Property TemperatureCelsius As Integer Implements IForecast.TemperatureCelsius
        Public Property Summary As String Implements IForecast.Summary
        Public Property WindSpeed As Integer
    End Class

    Public Class Forecasts
        Public Property Monday As IForecast
        Public Property Tuesday As Object
    End Class

End Namespace

Forecasts의 인스턴스를 직렬화할 경우 Tuesdayobject로 정의되어 있으므로 TuesdayWindSpeed 속성을 표시합니다.

var forecasts = new Forecasts
{
    Monday = new Forecast
    {
        Date = DateTime.Parse("2020-01-06"),
        TemperatureCelsius = 10,
        Summary = "Cool",
        WindSpeed = 8
    },
    Tuesday = new Forecast
    {
        Date = DateTime.Parse("2020-01-07"),
        TemperatureCelsius = 11,
        Summary = "Rainy",
        WindSpeed = 10
    }
};

options = new JsonSerializerOptions
{
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize(forecasts, options);
Dim forecasts1 As New Forecasts With {
    .Monday = New Forecast With {
        .[Date] = Date.Parse("2020-01-06"),
        .TemperatureCelsius = 10,
        .Summary = "Cool",
        .WindSpeed = 8
    },
    .Tuesday = New Forecast With {
        .[Date] = Date.Parse("2020-01-07"),
        .TemperatureCelsius = 11,
        .Summary = "Rainy",
        .WindSpeed = 10
    }
}

options = New JsonSerializerOptions With {
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(forecasts1, options)

다음 예제에서는 이전 코드의 결과로 생성되는 JSON을 보여줍니다.

{
  "Monday": {
    "Date": "2020-01-06T00:00:00-08:00",
    "TemperatureCelsius": 10,
    "Summary": "Cool"
  },
  "Tuesday": {
    "Date": "2020-01-07T00:00:00-08:00",
    "TemperatureCelsius": 11,
    "Summary": "Rainy",
    "WindSpeed": 10
  }
}

참고

이 문서는 역직렬화가 아닌 직렬화에 관한 것입니다. 다형 역직렬화는 .NET 7 이전 버전에서 지원되지 않지만 해결 방법으로 다형 역직렬화 지원의 예제와 같은 사용자 지정 변환기를 작성할 수 있습니다. .NET 7에서 다형 직렬화 및 역직렬화를 지원하는 방법에 대한 자세한 내용은 .NET 7에서 System.Text.Json을 사용하여 파생 클래스의 속성을 직렬화하는 방법을 참조하세요.

.NET 7부터 System.Text.Json은 특성 주석을 사용하여 다형 형식 계층 구조 직렬화 및 역직렬화를 지원합니다.

attribute 설명
JsonDerivedTypeAttribute 형식 선언에 배치되는 경우 지정된 하위 형식을 다형 직렬화로 옵트인해야 함을 나타냅니다. 또한 형식 판별자를 지정하는 기능도 노출합니다.
JsonPolymorphicAttribute 형식 선언에 배치하면 형식이 다형적으로 직렬화되어야 함을 나타냅니다. 또한 해당 형식에 대해 다형 직렬화 및 역직렬화를 구성하는 다양한 옵션을 노출합니다.

예를 들어 WeatherForecastBase 클래스와 WeatherForecastWithCity 파생 클래스가 있다고 가정해 봅시다.

[JsonDerivedType(typeof(WeatherForecastWithCity))]
public class WeatherForecastBase
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}
<JsonDerivedType(GetType(WeatherForecastWithCity))>
Public Class WeatherForecastBase
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
End Class
public class WeatherForecastWithCity : WeatherForecastBase
{
    public string? City { get; set; }
}
Public Class WeatherForecastWithCity
    Inherits WeatherForecastBase
    Public Property City As String
End Class

그리고 컴파일 시간에 Serialize<TValue> 메서드의 형식 인수가 WeatherForecastBase라고 가정하겠습니다.

options = new JsonSerializerOptions
{
    WriteIndented = true
};
jsonString = JsonSerializer.Serialize<WeatherForecastBase>(weatherForecastBase, options);
options = New JsonSerializerOptions With {
    .WriteIndented = True
}
jsonString = JsonSerializer.Serialize(WeatherForecastBase, options)

이 시나리오에서는 City 속성이 직렬화됩니다. weatherForecastBase 개체가 실제로 WeatherForecastWithCity 개체이기 때문입니다. 이 구성은 특히 런타임 형식이 WeatherForecastWithCity인 경우 WeatherForecastBase에 대해 다형 직렬화를 지원합니다.

{
  "City": "Milwaukee",
  "Date": "2022-09-26T00:00:00-05:00",
  "TemperatureCelsius": 15,
  "Summary": "Cool"
}

WeatherForecastBase로서 페이로드의 라운드트립은 지원되지만 런타임 형식 WeatherForecastWithCity로 구체화되지 않습니다. 대신 런타임 형식 WeatherForecastBase로 구체화됩니다.

WeatherForecastBase value = JsonSerializer.Deserialize<WeatherForecastBase>("""
    {
      "City": "Milwaukee",
      "Date": "2022-09-26T00:00:00-05:00",
      "TemperatureCelsius": 15,
      "Summary": "Cool"
    }
    """);

Console.WriteLine(value is WeatherForecastWithCity); // False
Dim value As WeatherForecastBase = JsonSerializer.Deserialize(@"
    {
      "City": "Milwaukee",
      "Date": "2022-09-26T00:00:00-05:00",
      "TemperatureCelsius": 15,
      "Summary": "Cool"
    }")

Console.WriteLine(value is WeatherForecastWithCity) // False

다음 섹션에서는 파생 형식의 라운드트립을 지원하도록 메타데이터를 추가하는 방법을 설명합니다.

다형 형식 판별자

다형 역직렬화를 사용하도록 설정하려면 파생 클래스에 대해 형식 판별자를 지정해야 합니다.

[JsonDerivedType(typeof(WeatherForecastBase), typeDiscriminator: "base")]
[JsonDerivedType(typeof(WeatherForecastWithCity), typeDiscriminator: "withCity")]
public class WeatherForecastBase
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string? Summary { get; set; }
}

public class WeatherForecastWithCity : WeatherForecastBase
{
    public string? City { get; set; }
}
<JsonDerivedType(GetType(WeatherForecastBase), "base")>
<JsonDerivedType(GetType(WeatherForecastWithCity), "withCity")>
Public Class WeatherForecastBase
    Public Property [Date] As DateTimeOffset
    Public Property TemperatureCelsius As Integer
    Public Property Summary As String
End Class

Public Class WeatherForecastWithCity
    Inherits WeatherForecastBase
    Public Property City As String
End Class

추가된 메타데이터, 특히 형식 판별자를 사용하여 직렬 변환기는 페이로드를 기본 형식 WeatherForecastBase에서 WeatherForecastWithCity 형식으로 직렬화 및 역직렬화할 수 있습니다. 직렬화는 형식 판별자 메타데이터와 함께 JSON을 내보냅니다.

WeatherForecastBase weather = new WeatherForecastWithCity
{
    City = "Milwaukee",
    Date = new DateTimeOffset(2022, 9, 26, 0, 0, 0, TimeSpan.FromHours(-5)),
    TemperatureCelsius = 15,
    Summary = "Cool"
}
var json = JsonSerializer.Serialize<WeatherForecastBase>(weather, options);
Console.WriteLine(json);
// Sample output:
//   {
//     "$type" : "withCity",
//     "City": "Milwaukee",
//     "Date": "2022-09-26T00:00:00-05:00",
//     "TemperatureCelsius": 15,
//     "Summary": "Cool"
//   }
Dim weather As WeatherForecastBase = New WeatherForecastWithCity With
{
    .City = "Milwaukee",
    .[Date] = New DateTimeOffset(2022, 9, 26, 0, 0, 0, TimeSpan.FromHours(-5)),
    .TemperatureCelsius = 15,
    .Summary = "Cool"
}
Dim json As String = JsonSerializer.Serialize(weather, options)
Console.WriteLine(json)
' Sample output:
'   {
'     "$type" : "withCity",
'     "City": "Milwaukee",
'     "Date": "2022-09-26T00:00:00-05:00",
'     "TemperatureCelsius": 15,
'     "Summary": "Cool"
'   }

형식 판별자를 사용하면 직렬 변환기는 페이로드를 WeatherForecastWithCity로 다형 역직렬화할 수 있습니다.

WeatherForecastBase value = JsonSerializer.Deserialize<WeatherForecastBase>(json);
Console.WriteLine(value is WeatherForecastWithCity); // True

참고

형식 판별자는 JSON 개체의 시작 부분에 배치되어야 하며, $id$ref와 같은 다른 메타데이터 속성과 함께 그룹화되어야 합니다.

Dim value As WeatherForecastBase = JsonSerializer.Deserialize(json)
Console.WriteLine(value is WeatherForecastWithCity) // True

형식 판별자 형식 혼합 및 일치

형식 판별자 식별자는 string 또는 int 양식에서 유효하므로 다음은 유효합니다.

[JsonDerivedType(typeof(WeatherForecastWithCity), 0)]
[JsonDerivedType(typeof(WeatherForecastWithTimeSeries), 1)]
[JsonDerivedType(typeof(WeatherForecastWithLocalNews), 2)]
public class WeatherForecastBase { }

var json = JsonSerializer.Serialize<WeatherForecastBase>(new WeatherForecastWithTimeSeries());
Console.WriteLine(json);
// Sample output:
//   {
//    "$type" : 1,
//    Omitted for brevity...
//   }
<JsonDerivedType(GetType(WeatherForecastWithCity), 0)>
<JsonDerivedType(GetType(WeatherForecastWithTimeSeries), 1)>
<JsonDerivedType(GetType(WeatherForecastWithLocalNews), 2)>
Public Class WeatherForecastBase
End Class

Dim json As String = JsonSerializer.Serialize(Of WeatherForecastBase)(New WeatherForecastWithTimeSeries())
Console.WriteLine(json)
' Sample output:
'  {
'    "$type" : 1,
'    Omitted for brevity...
'  }

API는 형식 판별자 구성 혼합 및 일치를 지원하지만 권장되지 않습니다. 일반적인 권장 사항은 모두 string 형식 판별자를 사용하거나, 모두 int 형식 판별자를 사용하거나, 판별자를 전혀 사용하지 않는 것입니다. 다음 예제에서는 형식 판별자 구성을 혼합 및 일치시키는 방법을 보여 줍니다.

[JsonDerivedType(typeof(ThreeDimensionalPoint), typeDiscriminator: 3)]
[JsonDerivedType(typeof(FourDimensionalPoint), typeDiscriminator: "4d")]
public class BasePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class ThreeDimensionalPoint : BasePoint
{
    public int Z { get; set; }
}

public sealed class FourDimensionalPoint : ThreeDimensionalPoint
{
    public int W { get; set; }
}
<JsonDerivedType(GetType(ThreeDimensionalPoint), 3)>
<JsonDerivedType(GetType(FourDimensionalPoint), "4d")>
Public Class BasePoint
    Public Property X As Integer
    Public Property Y As Integer
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
    Public Property Z As Integer
End Class

Public NotInheritable Class FourDimensionalPoint
    Inherits ThreeDimensionalPoint
    Public Property W As Integer
End Class

앞의 예제에서 BasePoint 형식은 형식 판별자가 없지만 ThreeDimensionalPoint 형식은 int 형식 판별자를 가지고 FourDimensionalPoint 형식은 string 형식 판별자를 갖습니다.

중요

다형 직렬화가 작동하려면 직렬화된 값의 형식이 다형 기본 형식이어야 합니다. 여기에는 기본 형식을 루트 수준 값을 직렬화할 때 제네릭 형식 매개 변수로 사용하거나, 직렬화된 속성의 선언된 형식으로 사용하거나, 직렬화된 컬렉션의 컬렉션 요소로 사용하는 것이 포함됩니다.

using System.Text.Json;
using System.Text.Json.Serialization;

PerformRoundTrip<BasePoint>();
PerformRoundTrip<ThreeDimensionalPoint>();
PerformRoundTrip<FourDimensionalPoint>();

static void PerformRoundTrip<T>() where T : BasePoint, new()
{
    var json = JsonSerializer.Serialize<BasePoint>(new T());
    Console.WriteLine(json);

    BasePoint? result = JsonSerializer.Deserialize<BasePoint>(json);
    Console.WriteLine($"result is {typeof(T)}; // {result is T}");
    Console.WriteLine();
}
// Sample output:
//   { "X": 541, "Y": 503 }
//   result is BasePoint; // True
//
//   { "$type": 3, "Z": 399, "X": 835, "Y": 78 }
//   result is ThreeDimensionalPoint; // True
//
//   { "$type": "4d", "W": 993, "Z": 427, "X": 508, "Y": 741 }
//   result is FourDimensionalPoint; // True
Imports System.Text.Json
Imports System.Text.Json.Serialization

Module Program
    Sub Main()
        PerformRoundTrip(Of BasePoint)()
        PerformRoundTrip(Of ThreeDimensionalPoint)()
        PerformRoundTrip(Of FourDimensionalPoint)()
    End Sub

    Private Sub PerformRoundTrip(Of T As {BasePoint, New})()
        Dim json = JsonSerializer.Serialize(Of BasePoint)(New T())
        Console.WriteLine(json)

        Dim result As BasePoint = JsonSerializer.Deserialize(Of BasePoint)(json)
        Console.WriteLine($"result is {GetType(T)}; // {TypeOf result Is T}")
        Console.WriteLine()
    End Sub
End Module
' Sample output:
'   { "X": 649, "Y": 754 }
'   result is BasePoint; // True
'
'   { "$type": 3, "Z": 247, "X": 814, "Y": 56 }
'   result is ThreeDimensionalPoint; // True
'
'   { "$type": "4d", "W": 427, "Z": 193, "X": 112, "Y": 935 }
'   result is FourDimensionalPoint; // True

형식 판별자 이름 사용자 지정

형식 판별자의 기본 속성 이름은 $type입니다. 속성 이름을 사용자 지정하려면 다음 예제와 같이 JsonPolymorphicAttribute를 사용합니다.

[JsonPolymorphic(TypeDiscriminatorPropertyName = "$discriminator")]
[JsonDerivedType(typeof(ThreeDimensionalPoint), typeDiscriminator: "3d")]
public class BasePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

public sealed class ThreeDimensionalPoint : BasePoint
{
    public int Z { get; set; }
}
<JsonPolymorphic(TypeDiscriminatorPropertyName:="$discriminator")>
<JsonDerivedType(GetType(ThreeDimensionalPoint), "3d")>
Public Class BasePoint
    Public Property X As Integer
    Public Property Y As Integer
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
    Public Property Z As Integer
End Class

이전 코드에서 JsonPolymorphic 특성은 "$discriminator" 값에 TypeDiscriminatorPropertyName을 구성합니다. 형식 판별자 이름을 구성했으면 다음 예제에서는 JSON으로 직렬화된 ThreeDimensionalPoint 형식을 보여줍니다.

BasePoint point = new ThreeDimensionalPoint { X = 1, Y = 2, Z = 3 };
var json = JsonSerializer.Serialize<BasePoint>(point);
Console.WriteLine(json);
// Sample output:
//  { "$discriminator": "3d", "X": 1, "Y": 2, "Z": 3 }
Dim point As BasePoint = New ThreeDimensionalPoint With { .X = 1, .Y = 2, .Z = 3 }
Dim json As String = JsonSerializer.Serialize(Of BasePoint)(point)
Console.WriteLine(json)
' Sample output:
'  { "$discriminator": "3d", "X": 1, "Y": 2, "Z": 3 }

형식 계층 구조의 속성과 충돌하는 JsonPolymorphicAttribute.TypeDiscriminatorPropertyName을 사용하지 마세요.

알 수 없는 파생 형식 처리

알 수 없는 파생 형식을 처리하려면 기본 형식에 주석을 사용하여 이러한 지원을 옵트인해야 합니다. 다음 형식 계층 구조를 예로 들 수 있습니다.

[JsonDerivedType(typeof(ThreeDimensionalPoint))]
public class BasePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class ThreeDimensionalPoint : BasePoint
{
    public int Z { get; set; }
}

public class FourDimensionalPoint : ThreeDimensionalPoint
{
    public int W { get; set; }
}
<JsonDerivedType(GetType(ThreeDimensionalPoint))>
Public Class BasePoint
    Public Property X As Integer
    Public Property Y As Integer
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
    Public Property Z As Integer
End Class

Public NotInheritable Class FourDimensionalPoint
    Inherits ThreeDimensionalPoint
    Public Property W As Integer
End Class

구성에서 FourDimensionalPoint에 대한 지원을 명시적으로 옵트인하지 않았으므로 FourDimensionalPoint 인스턴스를 BasePoint로 직렬화하려고 하면 런타임 예외가 발생합니다.

JsonSerializer.Serialize<BasePoint>(new FourDimensionalPoint()); // throws NotSupportedException
JsonSerializer.Serialize(Of BasePoint)(New FourDimensionalPoint()) ' throws NotSupportedException

다음과 같이 지정할 수 있는 JsonUnknownDerivedTypeHandling 열거형을 사용하여 기본 동작을 변경할 수 있습니다.

[JsonPolymorphic(
    UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
[JsonDerivedType(typeof(ThreeDimensionalPoint))]
public class BasePoint
{
    public int X { get; set; }
    public int Y { get; set; }
}

public class ThreeDimensionalPoint : BasePoint
{
    public int Z { get; set; }
}

public class FourDimensionalPoint : ThreeDimensionalPoint
{
    public int W { get; set; }
}
<JsonPolymorphic(
    UnknownDerivedTypeHandling:=JsonUnknownDerivedTypeHandling.FallBackToBaseType)>
<JsonDerivedType(GetType(ThreeDimensionalPoint))>
Public Class BasePoint
    Public Property X As Integer
    Public Property Y As Integer
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
    Public Property Z As Integer
End Class

Public NotInheritable Class FourDimensionalPoint
    Inherits ThreeDimensionalPoint
    Public Property W As Integer
End Class

기본 형식으로 대체하는 대신 FallBackToNearestAncestor 설정을 사용하여 가장 가까운 선언된 파생 형식의 계약으로 대체할 수 있습니다.

[JsonPolymorphic(
    UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
[JsonDerivedType(typeof(BasePoint)]
public interface IPoint { }

public class BasePoint : IPoint { }

public class ThreeDimensionalPoint : BasePoint { }
<JsonPolymorphic(
    UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)>
<JsonDerivedType(GetType(BasePoint)>
Public Interface IPoint
End Interface

Public Class BasePoint
    Inherits IPoint
End Class

Public Class ThreeDimensionalPoint
    Inherits BasePoint
End Class

앞의 예제와 같은 구성을 사용하면 ThreeDimensionalPoint 형식이 BasePoint로 직렬화됩니다.

// Serializes using the contract for BasePoint
JsonSerializer.Serialize<IPoint>(new ThreeDimensionalPoint());
' Serializes using the contract for BasePoint
JsonSerializer.Serialize(Of IPoint)(New ThreeDimensionalPoint())

그러나, 가장 가까운 상위 항목으로 다시 대체하는 것은 "다이아몬드" 모호성의 가능성이 있습니다. 예를 들어 다음 형식 계층 구조를 생각해 보겠습니다.

[JsonPolymorphic(
    UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)]
[JsonDerivedType(typeof(BasePoint))]
[JsonDerivedType(typeof(IPointWithTimeSeries))]
public interface IPoint { }

public interface IPointWithTimeSeries : IPoint { }

public class BasePoint : IPoint { }

public class BasePointWithTimeSeries : BasePoint, IPointWithTimeSeries { }
<JsonPolymorphic(
    UnknownDerivedTypeHandling:=JsonUnknownDerivedTypeHandling.FallBackToNearestAncestor)>
<JsonDerivedType(GetType(BasePoint))>
<JsonDerivedType(GetType(IPointWithTimeSeries))>
Public Interface IPoint
End Interface

Public Interface IPointWithTimeSeries
    Inherits IPoint
End Interface

Public Class BasePoint
    Implements IPoint
End Class

Public Class BasePointWithTimeSeries
    Inherits BasePoint
    Implements IPointWithTimeSeries
End Class

이 경우 BasePointWithTimeSeries 형식은 BasePoint 또는 IPointWithTimeSeries로 직렬화할 수 있습니다. 둘 다 직접 상위 항목이기 때문입니다. 이러한 모호성으로 인해 BasePointWithTimeSeries 인스턴스를 IPoint로 직렬화하려고 하면 NotSupportedException이 throw됩니다.

// throws NotSupportedException
JsonSerializer.Serialize<IPoint>(new BasePointWithTimeSeries());
' throws NotSupportedException
JsonSerializer.Serialize(Of IPoint)(New BasePointWithTimeSeries())

계약 모델을 사용하여 다형성 구성

특성 주석이 비실용적이거나 불가능한 사용 사례(예: 대규모 도메인 모델, 어셈블리 간 계층 구조 또는 타사 종속성의 계층 구조)의 경우 다형성을 구성하려면 계약 모델을 사용합니다. 계약 모델은 다음 예제와 같이 형식별로 다형 구성을 동적으로 제공하는 사용자 지정 DefaultJsonTypeInfoResolver 하위 클래스를 만들어 형식 계층 구조에서 다형성을 구성하는 데 사용할 수 있는 API 집합입니다.

public class PolymorphicTypeResolver : DefaultJsonTypeInfoResolver
{
    public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
    {
        JsonTypeInfo jsonTypeInfo = base.GetTypeInfo(type, options);

        Type basePointType = typeof(BasePoint);
        if (jsonTypeInfo.Type == basePointType)
        {
            jsonTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions
            {
                TypeDiscriminatorPropertyName = "$point-type",
                IgnoreUnrecognizedTypeDiscriminators = true,
                UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization,
                DerivedTypes =
                {
                    new JsonDerivedType(typeof(ThreeDimensionalPoint), "3d"),
                    new JsonDerivedType(typeof(FourDimensionalPoint), "4d")
                }
            };
        }

        return jsonTypeInfo;
    }
}
Public Class PolymorphicTypeResolver
    Inherits DefaultJsonTypeInfoResolver

    Public Overrides Function GetTypeInfo(
        ByVal type As Type,
        ByVal options As JsonSerializerOptions) As JsonTypeInfo

        Dim jsonTypeInfo As JsonTypeInfo = MyBase.GetTypeInfo(type, options)
        Dim basePointType As Type = GetType(BasePoint)

        If jsonTypeInfo.Type = basePointType Then
            jsonTypeInfo.PolymorphismOptions = New JsonPolymorphismOptions With {
                .TypeDiscriminatorPropertyName = "$point-type",
                .IgnoreUnrecognizedTypeDiscriminators = True,
                .UnknownDerivedTypeHandling =
                    JsonUnknownDerivedTypeHandling.FailSerialization
            }
            jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(
                New JsonDerivedType(GetType(ThreeDimensionalPoint), "3d"))
            jsonTypeInfo.PolymorphismOptions.DerivedTypes.Add(
                New JsonDerivedType(GetType(FourDimensionalPoint), "4d"))
        End If

        Return jsonTypeInfo
    End Function
End Class

추가 다형 직렬화 세부 정보

  • 다형 직렬화는 JsonDerivedTypeAttribute를 통해 명시적으로 옵트인된 파생 형식을 지원합니다. 선언되지 않은 형식으로 인해 런타임 예외가 발생합니다. JsonPolymorphicAttribute.UnknownDerivedTypeHandling 속성을 구성하여 이 동작을 변경할 수 있습니다.
  • 파생 형식에 지정된 다형 구성은 기본 형식의 다형 구성이 상속하지 않습니다. 기본 형식은 독립적으로 구성해야 합니다.
  • 다형 계층 구조는 interfaceclass 형식 모두에 대해 지원됩니다.
  • 형식 판별자를 사용하는 다형성은 개체, 컬렉션 및 사전 형식에 대한 기본 변환기를 사용하는 형식 계층 구조에서만 지원됩니다.
  • 다형성은 메타데이터 기반 소스 생성에서 지원되지만 빠른 경로 소스 생성에서는 지원되지 않습니다.

참고 항목