.NET 7 System.Text.Json多态序列化JsonDerivedType原理咨询
由Newtonsoft开发的JSON.NET对复杂对象的序列化与反序列化提供了出色的支持。很多开发者尝试使用System.Text.Json替代JSON.NET时,都会遇到多态处理的相关疑问,.NET 7版本开始正式引入了基于特性的多态支持,以下是基于.NET 7预览版与C# 11编写的多态处理示例代码:
// 运行环境要求:.NET 7 预览版、C# 11 using System.Text.Json.Serialization; var testPolymorphism = new TestPolymorphysm() { Animals = new List<Animal>() { new Fish() { Id = "fish1", Name = "GoldFish", Action = new ActionSwim() { DistanceSwam = 10 } }, new Dog() { Id = "dog1", Name = "Tom", Action = new ActionRun() { DistanceRan = 50 } } } }; // 序列化 var jsonSerialized = System.Text.Json.JsonSerializer.Serialize(testPolymorphism); Console.WriteLine(jsonSerialized); // 反序列化 var clonePolymorphysm = System.Text.Json.JsonSerializer.Deserialize<TestPolymorphysm>(jsonSerialized); Console.WriteLine(clonePolymorphysm); // === 数据模型定义 === class TestPolymorphysm { public List<Animal> Animals { get; set; } = new(); } [JsonDerivedType(derivedType: typeof(Dog), typeDiscriminator: "foo1")] [JsonDerivedType(derivedType: typeof(Fish), typeDiscriminator: "foo2")] abstract class Animal { public required string Id { get; set; } public required string Name { get; set; } } class Dog : Animal { public required IAction Action { get; set; } public AnimalType ExtensionType => AnimalType.Dog; } class Fish : Animal { public required IAction Action { get; set; } public AnimalType ExtensionType => AnimalType.Fish; } [JsonDerivedType(derivedType: typeof(ActionSwim), typeDiscriminator: "foo3")] [JsonDerivedType(derivedType: typeof(ActionRun), typeDiscriminator: "foo4")] interface IAction { } class ActionSwim : IAction { public required int DistanceSwam { get; set; } } class ActionRun : IAction { public required int DistanceRan { get; set; } } public enum AnimalType { Fish, Dog }
核心运行原理
很多人使用上述代码时都会产生疑问:为什么移除foo1、foo2、foo3、foo4这些typeDiscriminator配置后,代码就无法正常工作?本质上是多态序列化/反序列化的固有特性决定的:
多态场景下,声明的属性/集合类型,和实际存入的对象运行时类型并不一致。比如代码里定义的是List<Animal>,Animal是抽象类,本身不能直接实例化,实际集合里存的是Dog、Fish两个子类的实例;Action属性声明的是IAction接口,实际存的是ActionSwim、ActionRun两个实现类的对象。
序列化阶段程序可以直接通过反射拿到对象的真实运行时类型,不管声明类型是什么,都能把所有属性完整写入JSON,这一步哪怕不配置多态规则也不会出错。但反序列化阶段程序没有运行时类型信息,它只知道目标转换类型是Animal、IAction,既没法直接创建抽象类/接口的实例,也无法判断当前JSON片段对应的是哪个派生类,自然无法完成正确映射。JsonDerivedType特性中配置的typeDiscriminator,就是专门用来解决类型识别问题的标记:
- 序列化阶段:序列化器遇到配置了多态规则的基类/接口类型时,会自动在生成的JSON中加入一个特殊的类型鉴别字段(默认字段名为
$type),字段值就是配置的typeDiscriminator值,比如Dog实例对应"$type":"foo1"、Fish实例对应"$type":"foo2",直接打印序列化后的JSON字符串就能看到这些字段。 - 反序列化阶段:序列化器先读取JSON中的类型鉴别字段,再去基类/接口上配置的
JsonDerivedType映射表中匹配对应的派生类型,匹配成功后就实例化该具体类型,再完成后续的属性映射操作。
如果移除了typeDiscriminator配置,整个类型识别链路会直接断裂: - 序列化时不会在JSON中写入任何类型鉴别标记
- 反序列化时读不到类型标记,也没有对应的派生类型映射关系,只能尝试直接实例化声明的基类/接口,抽象类和接口本身不允许实例化,代码自然会抛出异常运行失败。
补充说明:就算基类不是抽象类、是可以直接实例化的普通类,没有类型鉴别标记的情况下,反序列化时只会生成基类实例,子类独有的属性会被直接忽略,同样达不到多态反序列化的预期效果。
内容的提问来源于stack exchange,提问作者Tono Nam

