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

在C#中使用Json.NET处理含动态键嵌套字典的JSON序列化与反序列化问题

在C#中使用Json.NET处理含动态键嵌套字典的JSON序列化与反序列化问题

问题背景

你现在在做一个C#项目,需要用Json.NET(Newtonsoft.Json)反序列化一段结构复杂的JSON——里面有多层嵌套的字典,各级字典的键都是动态变化的,尤其是attributes字段的格式特别灵活:有时候是嵌套字典,有时候是空数组,甚至内层的值还可能是空数组或者字符串。你原本定义了Dictionary<string, Dictionary<string, string>>类型的Attributes属性,并自定义了转换器,但运行时出现了类型转换错误,而且不想用dynamic类型,因为后续需要做XML的序列化和反序列化。

现有代码与报错信息

目标JSON结构

{
  "count": "57",
  "pages": 2,
  "page": 1,
  "list": [
    {
      "product_id": "30",
      "attributes": {
        "5": {
          "16": "YES",
          "15": "30°C",
          "14": "YES"
        },
        "6": {
          "30": "asd",
          "21": "Collar",
          "18": "Solid Color"
        }
      }
    },
    {
      "product_id": "31",
      "attributes": {
        "5": {
          "16": [],
          "15": [],
          "14": []
        },
        "6": {
          "30": [],
          "21": [],
          "18": []
        }
      }
    },
    {
      "product_id": "32",
      "attributes": {
        "5": [],
        "6": []
      }
    },
    {
      "product_id": "33",
      "attributes": []
    }
  ]
}

现有属性定义

[Newtonsoft.Json.JsonConverter(typeof(AttributesConverter))]
public Dictionary<string, Dictionary<string, string>> Attributes { get; set; }

自定义转换器(原版本)

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    JToken token = JToken.Load(reader);
    var result = new Dictionary<string, Dictionary<string, string>>();

    if (token.Type == JTokenType.Object)
    {
        foreach (var prop in token.Children<JProperty>())
        {
            var key = prop.Name;
            var value = prop.Value;

            if (value.Type == JTokenType.String)
            {
                var newDict = new Dictionary<string, string>();
                newDict[key] = value.ToString();
                result[key] = newDict;
            }
            else if (value.Type == JTokenType.Integer)
            {
                var newDict = new Dictionary<string, string>();
                newDict[key] = value.ToString();
                result[key] = newDict;
            }
            else if (token.Type == JTokenType.Array && !token.HasValues)
            {
                return new Dictionary<string, string>();
            }
            else if (value.Type == JTokenType.Object)
            {
                result[key] = value.ToObject<Dictionary<string, string>>();
            }
            else
            {
                // empty array
                // result[key] = value.ToObject<Dictionary<string, string>>();
            }
        }
    }
    else if (token.Type == JTokenType.Array && !token.HasValues)
    {
        return new Dictionary<string, string>();
    }
    else
    {
        return null;
    }

    return result;
}

报错信息

InvalidCastException: Unable to cast object of type 'System.Collections.Generic.Dictionary`2[System.String,System.Collections.Generic.Dictionary`2[System.String,System.Object]]' to type 'System.Collections.Generic.Dictionary`2[System.String,System.Collections.Generic.Dictionary`2[System.String,System.String]]'.

修正后的解决方案

问题出在你的转换器没有正确处理空数组和值为数组的情况,而且在某些分支返回的类型和目标类型不匹配(比如返回Dictionary<string, string>而不是外层的Dictionary<string, Dictionary<string, string>>)。下面是修正后的转换器代码,能处理所有你遇到的attributes格式:

public class AttributesConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Dictionary<string, Dictionary<string, string>>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        var outerDict = new Dictionary<string, Dictionary<string, string>>();

        // 处理最外层是空数组的情况
        if (token.Type == JTokenType.Array && !token.HasValues)
        {
            return outerDict;
        }

        // 处理外层是对象的情况
        if (token.Type == JTokenType.Object)
        {
            foreach (var prop in token.Children<JProperty>())
            {
                string outerKey = prop.Name;
                JToken innerValue = prop.Value;
                var innerDict = new Dictionary<string, string>();

                // 内层是空数组
                if (innerValue.Type == JTokenType.Array && !innerValue.HasValues)
                {
                    outerDict[outerKey] = innerDict;
                    continue;
                }

                // 内层是对象(嵌套字典)
                if (innerValue.Type == JTokenType.Object)
                {
                    foreach (var innerProp in innerValue.Children<JProperty>())
                    {
                        string innerKey = innerProp.Name;
                        JToken valueToken = innerProp.Value;

                        // 处理内层值是空数组的情况,转成空字符串或者你需要的默认值
                        if (valueToken.Type == JTokenType.Array && !valueToken.HasValues)
                        {
                            innerDict[innerKey] = string.Empty;
                        }
                        else
                        {
                            // 把其他类型(字符串、数字等)转成字符串
                            innerDict[innerKey] = valueToken.ToString();
                        }
                    }
                    outerDict[outerKey] = innerDict;
                    continue;
                }

                // 内层是单个值(字符串、数字等)
                innerDict[outerKey] = innerValue.ToString();
                outerDict[outerKey] = innerDict;
            }
        }

        return outerDict;
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        // 如果需要序列化回JSON,这里可以实现对应的逻辑
        // 比如把空字典转成空数组,或者保持对象格式,根据你的需求调整
        var outerDict = value as Dictionary<string, Dictionary<string, string>>;
        if (outerDict == null || !outerDict.Any())
        {
            writer.WriteStartArray();
            writer.WriteEndArray();
            return;
        }

        writer.WriteStartObject();
        foreach (var outerKvp in outerDict)
        {
            writer.WritePropertyName(outerKvp.Key);
            var innerDict = outerKvp.Value;
            if (!innerDict.Any())
            {
                writer.WriteStartArray();
                writer.WriteEndArray();
                continue;
            }

            writer.WriteStartObject();
            foreach (var innerKvp in innerDict)
            {
                writer.WritePropertyName(innerKvp.Key);
                writer.WriteValue(innerKvp.Value);
            }
            writer.WriteEndObject();
        }
        writer.WriteEndObject();
    }
}

关键修正点说明

  1. 类型匹配:确保所有分支都返回Dictionary<string, Dictionary<string, string>>类型,避免返回错误的字典层级。
  2. 空数组处理:无论是最外层的attributes是空数组,还是内层的属性值是空数组,都统一转成对应的空字典或者空字符串,保证类型一致性。
  3. 值类型统一:把所有非数组类型的值都转成字符串,确保Dictionary<string, string>的内层字典值类型统一。
  4. 序列化支持:补充了WriteJson方法,这样后续你需要把对象序列化回JSON或者XML时,都能保持格式的正确性(XML序列化需要你的字典类型是可序列化的,这个结构完全符合要求)。

这样修改后,你就能正确反序列化所有格式的attributes字段,同时不需要使用dynamic类型,完美支持后续的XML序列化/反序列化需求。

备注:内容来源于stack exchange,提问作者Hub

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 07:04:53