You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 7 System.Text.Json多态序列化JsonDerivedType原理咨询

System.Text.Json 多态序列化/反序列化中 typeDiscriminator 的作用原理

由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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 15:54:17