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

C#如何实现支持多类型字段的JSON类建模与序列化反序列化

C# 实现带鉴别字段的多态JSON编解码方案

你遇到的是标准的判别联合(Discriminated Union)JSON序列化场景,和Pydantic根据Literal类型字段自动选择对应模型的逻辑完全一致,以下是最务实的实现方式,优先适配你正在使用的Newtonsoft.Json:

方案1:基于Newtonsoft.Json实现(零额外依赖)

你写的类结构思路完全正确,只需要补一个自定义JSON转换器处理kind鉴别字段即可,步骤如下:

  1. 先优化类定义,避免重复可写的kind字段导致人为赋值错误,用只读属性固定鉴别值,同时修正你代码里把Bar误写为Buzz的问题:
    public interface IPayload
    {
        // 只读属性固定鉴别值,和Pydantic的Literal效果一致
        string Kind { get; }
    }
    
    public class FooPayload : IPayload
    {
        public string Kind => "foo";
        public int Fizz { get; set; }
    }
    
    public class BarPayload : IPayload
    {
        public string Kind => "bar";
        public string Buzz { get; set; }
    }
    
    public class Message
    {
        public IPayload Payload { get; set; }
    }
    
  2. 实现针对IPayload类型的自定义JSON转换器,核心逻辑是反序列化时先读取kind字段的值,匹配到对应类型再完成反序列化,序列化时直接按实际对象类型输出:
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    
    public class PayloadTypeConverter : JsonConverter<IPayload>
    {
        public override IPayload ReadJson(JsonReader reader, Type objectType, IPayload existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            // 先将当前JSON节点加载为可查询对象,读取鉴别字段
            JObject jsonObject = JObject.Load(reader);
            string discriminator = jsonObject["kind"]?.Value<string>();
    
            return discriminator switch
            {
                "foo" => jsonObject.ToObject<FooPayload>(serializer),
                "bar" => jsonObject.ToObject<BarPayload>(serializer),
                _ => throw new JsonSerializationException($"无法识别的payload类型:{discriminator}")
            };
        }
    
        public override void WriteJson(JsonWriter writer, IPayload value, JsonSerializer serializer)
        {
            // 序列化时直接按实际实现类型写入即可,Kind属性会自动输出固定值
            serializer.Serialize(writer, value);
        }
    }
    
  3. 注册转换器即可正常使用,两种注册方式选其一即可:
    • 单属性生效:在Message类的Payload属性上添加特性
      public class Message
      {
          [JsonConverter(typeof(PayloadTypeConverter))]
          public IPayload Payload { get; set; }
      }
      
    • 全局生效:初始化序列化配置时加入转换器,所有用到IPayload的位置自动生效
      var serializeSettings = new JsonSerializerSettings
      {
          // 如果需要匹配小驼峰JSON字段名,加上这行配置即可,不需要修改C#类的帕斯卡命名
          ContractResolver = new CamelCasePropertyNamesContractResolver(),
          Converters = { new PayloadTypeConverter() }
      };
      
      // 反序列化
      Message msg = JsonConvert.DeserializeObject<Message>(rawJson, serializeSettings);
      // 序列化
      string outputJson = JsonConvert.SerializeObject(msg, serializeSettings);
      

后续新增Payload类型只需要新增对应实现类,在转换器的switch分支加一行匹配规则即可,维护成本很低。

方案2:更简洁的原生方案(适用于.NET 7+)

如果你可以升级到.NET 7及以上版本,自带的System.Text.Json原生支持多态序列化,不需要手动写自定义转换器,只需要在接口定义上加上特性标记即可:

using System.Text.Json.Serialization;

[JsonDerivedType(typeof(FooPayload), typeDiscriminator: "foo")]
[JsonDerivedType(typeof(BarPayload), typeDiscriminator: "bar")]
public interface IPayload
{
    string Kind { get; }
}

配置完成后直接调用System.Text.Json的序列化/反序列化方法即可自动识别类型,代码量更少。

补充说明

C#目前没有原生的字符串字面量类型,用只读属性返回固定字符串的方式,可以达到和Pydantic的Literal["foo"]接近的效果,避免人为给kind字段赋错误值。如果项目里有大量联合类型场景,也可以引入OneOf这类库实现真正的类型安全联合,但对于你当前的场景,上面的Newtonsoft.Json方案已经足够轻量够用,没有额外依赖。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 01:30:46