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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:14:05