在.NET中,我们经常需要在序列化和反序列化时处理多态。这意味着我们有一个基类或接口,以及一些实现或继承这个基类或接口的类,我们希望能够透明地序列化和反序列化它们。在.NET 7+和.NET 6以下,实现这一点的方式略有不同。
例如对下面这样的关系,需要根据父类中定义 Type 的不同分别使用不同的子类进行序列化/反序列化:
public abstract class Parent
{
public string Type { get; set; }
}
// Type = "ChildA"
public class ChildA : Parent
{
public string PropertyA { get; set; }
}
// Type="ChildB"
public class ChildB : Parent
{
public string PropertyB { get; set; }
}
.NET 7+ 的实现方法
.NET 7 之后,微软提供了官方的多态序列化支持,JsonPolymorphicAttribute,JsonDerivedTypeAttribute(文档).
这两个特性使用起来非常方便,直接在父类中指定序列化的逻辑即可,具体如下:
using System.Text.Json.Serialization;
[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(ChildA), "ChildA")]
[JsonDerivedType(typeof(ChildB), "ChildB")]
public abstract class Parent
{
public string Type { get; set; }
}
上面这段代码中,JsonPolymorphic
标识区分子类的字段,这个字段不一定在 C# 中定义,但在 Json 中要有,并且一定要在第一字段, 否则会报错。
JsonPolymorphic 如果省略,默认会以
$type
为标识字段
JsonDerivedType
则定义了根据标识字段的值,来选择不同的值对象进行反序列化。
.NET 6 以下的实现方法
而在.NET 6以下,System.Text.Json 没有官方的多态序列化的实现,所以只能另寻他路:
- 寻找第三方开源库
- 自定实现转换器
先说第一种,使用开源的库:JsonSubTypes
这个库实现了强大的配置功能,包括类似 JsonPolymorphic/JsonDerivedType 的用法, 具体使用方法在仓库文档中写得比较详细,这里不再赘述。
下面说说自定义实现转换器的方法
通过自定义 Converter, 可以对指定的类型进行自定义的转换逻辑,这样在 ParentConverter 中实现多态转换的逻辑,只需要在父类中指定转换器即可:
[JsonConverter(typeof(ParentConverter))]
public abstract class Parent
{
public string CommonProperty { get; set; }
}
这个方法缺点是灵活性差,每个类型都需要定义针对的 Converter (当然可以抽象,不过就和上面两种方法一样了,没必要重新造轮子),当然也有其优点,那就是功能强大,可以实现任意的转换逻辑,多态序列化只是其中一个具体的场景。
在一些具体的案例中,还是有必要学习这种实现思路的。 比如需要附加额外的转换逻辑,或者项目对第三方库的引用限制较多,又或者整个项目需要转换的类型只有一两处等。这些场景下是否有必要引入一个第三方库还是值得商讨的。
ParentConverter 的实现:
在微软的 官方文档 中提供了自定义转换器的实现示例, 但是该示例中每个子类属性都要单独处理,使用比较不方便,且扩展性很差。 所以我们可以牺牲一点点性能实现一个更加通用和优雅的方法:
using System.Text.Json;
using System.Text.Json.Serialization;
public class ParentConverter : JsonConverter<Parent>
{
public override Parent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var document = JsonDocument.ParseValue(ref reader);
var root = document.RootElement;
if (!root.TryGetProperty("type", out var typeElement) || typeElement.ValueKind != JsonValueKind.String)
{
throw new JsonException("Type property should be a string");
}
var type = typeElement.GetString();
return type switch
{
"ChildA" => JsonSerializer.Deserialize<ChildA>(root.GetRawText(), options),
"ChildB" => JsonSerializer.Deserialize<ChildB>(root.GetRawText(), options),
_ => throw new JsonException("Unknown type"),
};
}
public override void Write(Utf8JsonWriter writer, Parent value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value, value.GetType(), options);
}
}
参考链接:
- https://learn.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json/polymorphism?pivots=dotnet-8-0
- https://learn.microsoft.com/zh-cn/dotnet/standard/serialization/system-text-json/converters-how-to?pivots=dotnet-6-0#support-polymorphic-deserialization
- https://github.com/manuc66/JsonSubTypes