C#如何实现支持多类型字段的JSON类建模与序列化反序列化
C# 实现带鉴别字段的多态JSON编解码方案
你遇到的是标准的判别联合(Discriminated Union)JSON序列化场景,和Pydantic根据Literal类型字段自动选择对应模型的逻辑完全一致,以下是最务实的实现方式,优先适配你正在使用的Newtonsoft.Json:
方案1:基于Newtonsoft.Json实现(零额外依赖)
你写的类结构思路完全正确,只需要补一个自定义JSON转换器处理kind鉴别字段即可,步骤如下:
- 先优化类定义,避免重复可写的
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; } } - 实现针对
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); } } - 注册转换器即可正常使用,两种注册方式选其一即可:
- 单属性生效:在
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
相关产品推荐
相关产品推荐

