共用方式為


如何使用 System.Text.Json 序列化衍生類別的屬性

在本文中,您將瞭解如何使用 System.Text.Json 命名空間串行化衍生類別的屬性。

序列化衍生類別的屬性

從 .NET 7 開始,System.Text.Json 支援使用屬性註釋進行多型型別階層序列化和還原序列化。

屬性 描述
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)

在此案例中,weatherForecastBase 物件實際上是 WeatherForecastWithCity 物件,因此不會序列化 City 屬性。 此設定會啟用 WeatherForecastBase 的多型序列化,特別是當執行階段型別為 WeatherForecastWithCity 時:

{
  "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
Dim value As WeatherForecastBase = JsonSerializer.Deserialize(json)
Console.WriteLine(value is WeatherForecastWithCity) // True

注意

根據預設, $type 歧視性必須放在 JSON 物件的開頭,並與其他元數據屬性分組,例如 $id$ref。 如果您要從外部 API 讀取資料,以將歧視性放在 $type JSON 物件中間,請將 設定 JsonSerializerOptions.AllowOutOfOrderMetadataPropertiestrue

JsonSerializerOptions options = new() { AllowOutOfOrderMetadataProperties = true };
JsonSerializer.Deserialize<Base>("""{"Name":"Name","$type":"derived"}""", options);

當您啟用此旗標時請小心,因為執行非常大型 JSON 物件的串流還原串行化時,可能會導致過度緩衝處理(和記憶體不足失敗)。

混合和比對型別鑑別子格式

型別鑑別子識別碼不論是 stringint 格式都有效,因此下列是有效的:

[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 型別鑑別子,而且 FourDimensionalPointstring 型別鑑別子。

重要

若要讓多型序列化能夠運作,序列化值的型別應該屬於多型基底類型。 這包括在序列化根層級值時,使用基底類型作為泛型型別參數、作為序列化屬性的宣告型別,或作為列化集合中的集合元素。

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 屬性會將 TypeDiscriminatorPropertyName 設定為 "$discriminator" 值。 設定型別鑑別子名稱後,下列範例會顯示序列化為 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 型別可以序列化為 BasePointIPointWithTimeSeries,因為這兩者都是直接上階。 嘗試將 BasePointWithTimeSeries 的執行個體序列化為 IPoint 時,這個模棱兩可的情況會導致擲回 NotSupportedException

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

使用合約模型設定多型

對於屬性註釋不切實際或不可能的使用案例 (例如大型領域模型、跨組件階層,或第三方相依性中的階層),設定多型使用合約模型。 合約模型是一組 API,可用來在型別階層中設定多型,方法是建立可為每個型別動態提供多型設定的自訂 DefaultJsonTypeInfoResolver 子類別,如下列範例所示:

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 型別都支援多型階層。
  • 使用型別鑑別子的多型僅支援使用物件、集合和字典型別之預設轉換器的型別階層。
  • 中繼資料型來源產生支援多型,但不支援快速路徑來源產生。

另請參閱