如何用C#类适配含可变嵌套覆盖项的JSON格式?(仅用Json.NET)
解决嵌套JSON结构的灵活序列化问题(仅用Json.NET)
我完全理解你的痛点——用自动生成的类太死板,用字典又会多出不必要的属性名,还得在嵌套层级实现根对象那种"平铺"效果对吧?刚好可以用Json.NET的自定义JsonConverter来搞定,完全不需要额外第三方库,还能保留强类型的优势。
先明确一下目标JSON的结构(我根据你的描述还原的):
{ "request": { "coverages": { "path": "/some/fixed/path", "broadcastCoverage": { "Type": "Broadcast", "Channel": "CCTV-1" }, "internetCoverage": { "Type": "Internet", "Domain": "example.com" } } } }
下面是具体实现步骤:
1. 定义强类型模型
先把覆盖项的基类、具体子类,以及包含coverages的容器类定义好,注意给容器类加上自定义转换器标记:
// 覆盖项基类(放所有覆盖类型的公共属性) public class CoverageBase { public string Type { get; set; } } // 具体的广播覆盖类 public class BroadcastCoverage : CoverageBase { public string Channel { get; set; } } // 具体的网络覆盖类 public class InternetCoverage : CoverageBase { public string Domain { get; set; } } // 核心的Coverages容器类,用自定义转换器处理序列化/反序列化 [JsonConverter(typeof(CoveragesConverter))] public class Coverages { // 固定的path字段 public string Path { get; set; } // 存放可变覆盖项的字典(序列化后不会显示这个属性名) public Dictionary<string, CoverageBase> VariableCoverages { get; set; } = new(); } // 请求根类 public class RequestModel { public Coverages Request { get; set; } }
2. 实现自定义JsonConverter
这个转换器的作用就是:
- 序列化时:把
Path和VariableCoverages里的键值对平铺到同一个JSON对象中 - 反序列化时:把
path字段单独提取,剩下的键值对映射到VariableCoverages字典,并实例化对应的子类
public class CoveragesConverter : JsonConverter<Coverages> { public override Coverages ReadJson(JsonReader reader, Type objectType, Coverages existingValue, bool hasExistingValue, JsonSerializer serializer) { var jsonObj = JObject.Load(reader); var coverages = new Coverages(); // 提取固定的path字段 coverages.Path = jsonObj["path"]?.ToString(); jsonObj.Remove("path"); // 处理所有可变覆盖项 foreach (var prop in jsonObj.Properties()) { CoverageBase coverage; // 这里可以根据键名或者覆盖项里的Type字段来判断具体类型 // 我这里用键名判断,你也可以改成根据Type字段更灵活的逻辑 switch (prop.Name) { case "broadcastCoverage": coverage = prop.Value.ToObject<BroadcastCoverage>(serializer); break; case "internetCoverage": coverage = prop.Value.ToObject<InternetCoverage>(serializer); break; // 新增其他覆盖类型时,在这里加case就行 default: coverage = prop.Value.ToObject<CoverageBase>(serializer); break; } coverages.VariableCoverages.Add(prop.Name, coverage); } return coverages; } public override void WriteJson(JsonWriter writer, Coverages value, JsonSerializer serializer) { writer.WriteStartObject(); // 先写固定的path字段 writer.WritePropertyName("path"); serializer.Serialize(writer, value.Path); // 再把字典里的可变覆盖项逐个写入 foreach (var kvp in value.VariableCoverages) { writer.WritePropertyName(kvp.Key); serializer.Serialize(writer, kvp.Value); } writer.WriteEndObject(); } }
3. 使用示例
现在你就可以灵活地添加/移除覆盖项,序列化后完全符合目标格式:
// 构建请求对象 var request = new RequestModel { Request = new Coverages { Path = "/api/coverage/v1", VariableCoverages = new() { { "broadcastCoverage", new BroadcastCoverage { Type = "Broadcast", Channel = "CCTV-1" } }, { "internetCoverage", new InternetCoverage { Type = "Internet", Domain = "example.com" } } // 想加其他覆盖项直接往字典里加就行 } } }; // 序列化输出 var json = JsonConvert.SerializeObject(request, Formatting.Indented); // 反序列化测试 var deserialized = JsonConvert.DeserializeObject<RequestModel>(json);
额外优化点
如果不想通过键名判断覆盖类型,还可以改成根据覆盖项里的Type字段来实例化子类,比如在ReadJson里:
var typeValue = prop.Value["Type"]?.ToString(); switch (typeValue) { case "Broadcast": coverage = prop.Value.ToObject<BroadcastCoverage>(serializer); break; case "Internet": coverage = prop.Value.ToObject<InternetCoverage>(serializer); break; // ... }
这样即使键名变化,只要Type字段正确,也能正确反序列化,灵活性更高。
内容的提问来源于stack exchange,提问作者RSchmitt
相关产品推荐
相关产品推荐

