语言

如何使用 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)

在这种情况下,因为 City 对象实际上是 weatherForecastBase 对象,所以会序列化 WeatherForecastWithCity 属性。 此配置启用 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

通过添加的元数据,特别是类型鉴别器,序列化程序可以将有效负载从其基类型 WeatherForecastWithCity 序列化和反序列化为 WeatherForecastBase 类型。 序列化会在发出类型鉴别器元数据时同时发出 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 读取的数据在 JSON 对象中间放置了 $type 鉴别器,请将 JsonSerializerOptions.AllowOutOfOrderMetadataProperties 设置为 true:

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

启用此标志时要小心,因为在对超大 JSON 对象进行流反序列化时,它可能会导致缓冲过大(和内存不足故障)。

混合和匹配类型鉴别器格式

类型鉴别器标识符的有效形式为 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 属性将 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 类型可序列化为 BasePoint 或 IPointWithTimeSeries,因为它们都是直接上级。 当尝试将 NotSupportedException 的实例序列化为 BasePointWithTimeSeries 时,此歧义将导致引发 IPoint。

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

从封闭层次结构推断多态性

从 .NET 11 开始,System.Text.Json可以从 C# 15 封闭层次结构推断派生类型。 通过 JsonPolymorphicAttribute.InferClosedTypePolymorphism以下方法在一个层次结构上启用推理:

[JsonPolymorphic(InferClosedTypePolymorphism = true)]
public closed class Shape { }
public sealed class Circle : Shape { }
public sealed class Square : Shape { }

序列化程序注册每个派生类型,并将其简单类型名称用作字符串鉴别器。 例如, Circle 有效负载包含 "$type":"Circle"。

若要为选项实例处理的每个封闭层次结构启用推理,请设置 JsonSerializerOptions.InferClosedTypePolymorphism:

var options = new JsonSerializerOptions
{
    InferClosedTypePolymorphism = true
};

对于源生成,请设置 JsonSourceGenerationOptionsAttribute.InferClosedTypePolymorphism:

[JsonSourceGenerationOptions(InferClosedTypePolymorphism = true)]
[JsonSerializable(typeof(Shape))]
internal partial class AppJsonContext : JsonSerializerContext;

使用生成的上下文时,对源生成属性设置推理。 仅启用运行时 JsonSerializerOptions 属性不能添加源生成器未发出的派生类型元数据。

类型级别属性优先于全局设置。 JsonPolymorphicAttribute设置为InferClosedTypePolymorphism = false选择一个层次结构退出全局设置。

显式 JsonDerivedTypeAttribute 注册将替换该层次结构的推理。 它们不会扩展推断的列表。 InferClosedTypePolymorphism = true应用于未closed引发InvalidOperationException基于反射的序列化的类型,并生成源生成错误。 具有重复简单名称的推断类型会产生歧视性冲突,并且每个推断的类型必须至少与封闭基一样易于访问。

配置打开的泛型派生类型

从 .NET 11 开始,当序列化程序可以从序列化基类型解析唯一的封闭类型时,JsonDerivedTypeAttribute接受开放泛型派生类型:

[JsonDerivedType(typeof(Derived<>), "derived")]
public class Base<T>;
public class Derived<T> : Base<T>;
<JsonDerivedType(GetType(Derived(Of )), "derived")>
Public Class Base(Of T)
End Class
Public Class Derived(Of T)
    Inherits Base(Of T)
End Class

对于 Base<int>,序列化程序注册 Derived<int>。 同一分辨率支持泛型接口、重新排序的类型参数、嵌套泛型参数、数组和派生类型,这些类型将某些基类型参数固定到具体类型。

每个派生类型参数都必须从封闭基类型推断,替换必须明确,生成的类型必须满足其泛型约束。 基于反射的序列化会引发 InvalidOperationException 不受支持的专用化。 源生成报告 SYSLIB1229,当序列化程序配置它时,生成的层次结构仍然失败。 取消警告不会使注册生效。

使用协定模型配置多形性

对于属性注释不切实际或不可能的用例(例如大型域模型、跨程序集层次结构或第三方依赖项中的层次结构),可以使用协定模型配置多形性。 协定模型是一组 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 属性更改此行为。
  • 派生类型中指定的多态配置不是由基类型中的多态配置继承的。 基类型必须独立配置。
  • interface 和 class 类型都支持多态层次结构。
  • 使用类型鉴别器的多形性仅支持为对象、集合和字典类型使用默认转换器的类型层次结构。
  • 基于元数据的源生成支持多形性,快速路径源生成但不支持。

请参阅