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

System.Text.Json/Newtonsoft实现部分子类自定义类型鉴别器需求

问题描述

现有类型结构如下:

public class Person { public string Name { get; set; } }
public class Adult : Person { public string Occupation { get; set; } }
... // 更多子类

需要实现的序列化结果(注意第二个条目无__type字段):

[{"__type":"P","Name":"AbcPerson"},{"Occupation":"","Name":"XyzPerson"}]

此前通过System.Web.Script.Serialization.JavaScriptTypeResolver可轻松实现,现需改用System.Text.Json。目前使用了以下特性:

[JsonDerivedType(typeof(Person), typeDiscriminator: "P")]
[JsonDerivedType(typeof(Adult))]

但子类数量多且持续新增,仅需为部分子类设置特定鉴别器,其余子类能否使用默认规则?同时也接受Newtonsoft.Json的实现方案,但需将默认的$type字段改为__type。


一、System.Text.Json 实现方案

核心思路

通过自定义JsonConverter<Person>实现精细化控制:仅为标记了自定义特性的类型输出指定的__type鉴别器,其他子类直接序列化、不输出鉴别器字段。新增子类时,只要不需要自定义鉴别器就无需修改配置。

代码实现

  1. 定义自定义鉴别器特性
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class CustomTypeDiscriminatorAttribute : Attribute
{
    public string Discriminator { get; }
    public CustomTypeDiscriminatorAttribute(string discriminator)
    {
        Discriminator = discriminator;
    }
}

// 给需要自定义鉴别器的类型标记特性
[CustomTypeDiscriminator("P")]
public class Person { public string Name { get; set; } }
public class Adult : Person { public string Occupation { get; set; } }
// 其他子类无需额外标记
  1. 实现自定义Json转换器
public class PersonConverter : JsonConverter<Person>
{
    // 反序列化逻辑:根据__type字段匹配对应类型
    public override Person Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using var doc = JsonDocument.ParseValue(ref reader);
        var root = doc.RootElement;
        
        if (root.TryGetProperty("__type", out var discriminatorElem))
        {
            var targetType = discriminatorElem.GetString() switch
            {
                "P" => typeof(Person),
                // 可扩展其他自定义鉴别器的类型映射
                _ => typeof(Adult) // 默认匹配子类
            };
            return (Person)JsonSerializer.Deserialize(root.GetRawText(), targetType, options);
        }
        // 无__type字段时默认反序列化为Adult
        return JsonSerializer.Deserialize<Adult>(root.GetRawText(), options);
    }

    // 序列化逻辑:仅标记了自定义特性的类型输出__type字段
    public override void Write(Utf8JsonWriter writer, Person value, JsonSerializerOptions options)
    {
        var attr = value.GetType().GetCustomAttribute<CustomTypeDiscriminatorAttribute>();
        if (attr != null)
        {
            writer.WriteStartObject();
            writer.WriteString("__type", attr.Discriminator);
            // 序列化其他属性,避免重复写入__type
            var element = JsonSerializer.SerializeToElement(value, value.GetType(), options);
            foreach (var prop in element.EnumerateObject())
            {
                if (prop.Name != "__type") prop.WriteTo(writer);
            }
            writer.WriteEndObject();
        }
        else
        {
            // 无自定义鉴别器的类型直接序列化,不输出__type
            JsonSerializer.Serialize(writer, value, value.GetType(), options);
        }
    }
}
  1. 使用转换器完成序列化
var options = new JsonSerializerOptions
{
    Converters = { new PersonConverter() },
    // 可添加其他配置如忽略空值等
};

var people = new List<Person>
{
    new Person { Name = "AbcPerson" },
    new Adult { Name = "XyzPerson", Occupation = "" }
};

var json = JsonSerializer.Serialize(people, options);
// 输出结果:[{"__type":"P","Name":"AbcPerson"},{"Occupation":"","Name":"XyzPerson"}]

二、Newtonsoft.Json 实现方案

核心思路

通过自定义ISerializationBinder将默认的$type字段替换为__type,再配合自定义转换器控制哪些类型输出鉴别器字段,实现需求效果。

代码实现

  1. 自定义序列化绑定器(修改鉴别器字段名)
public class CustomSerializationBinder : ISerializationBinder
{
    // 序列化时映射类型到鉴别器名称
    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        if (serializedType == typeof(Person))
        {
            typeName = "P";
            assemblyName = null; // 无需输出程序集名
            return;
        }
        // 其他类型默认输出类名
        typeName = serializedType.Name;
        assemblyName = null;
    }

    // 反序列化时映射鉴别器名称到类型
    public Type BindToType(string assemblyName, string typeName)
    {
        return typeName switch
        {
            "P" => typeof(Person),
            "Adult" => typeof(Adult),
            // 可扩展其他类型映射
            _ => Type.GetType($"{typeName}, {Assembly.GetExecutingAssembly().FullName}")
        };
    }
}
  1. 自定义转换器控制鉴别器输出
public class PersonNewtonsoftConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(Person).IsAssignableFrom(objectType);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var jo = JObject.FromObject(value);
        // 仅为Person类型添加__type字段
        if (value.GetType() == typeof(Person))
        {
            jo.AddFirst(new JProperty("__type", "P"));
        }
        jo.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jo = JObject.Load(reader);
        if (jo.TryGetValue("__type", out var typeToken))
        {
            var targetType = typeToken.Value<string>() switch
            {
                "P" => typeof(Person),
                _ => typeof(Adult)
            };
            return jo.ToObject(targetType, serializer);
        }
        return jo.ToObject<Adult>(serializer);
    }
}
  1. 配置并执行序列化
var settings = new JsonSerializerSettings
{
    SerializationBinder = new CustomSerializationBinder(),
    Converters = { new PersonNewtonsoftConverter() },
    // 可添加其他配置如格式化输出等
};

var people = new List<Person>
{
    new Person { Name = "AbcPerson" },
    new Adult { Name = "XyzPerson", Occupation = "" }
};

var json = JsonConvert.SerializeObject(people, settings);
// 输出结果:[{"__type":"P","Name":"AbcPerson"},{"Occupation":"","Name":"XyzPerson"}]

内容的提问来源于stack exchange,提问作者Yola

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 11:27:30