在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(); } }
关键修正点说明
- 类型匹配:确保所有分支都返回
Dictionary<string, Dictionary<string, string>>类型,避免返回错误的字典层级。 - 空数组处理:无论是最外层的
attributes是空数组,还是内层的属性值是空数组,都统一转成对应的空字典或者空字符串,保证类型一致性。 - 值类型统一:把所有非数组类型的值都转成字符串,确保
Dictionary<string, string>的内层字典值类型统一。 - 序列化支持:补充了
WriteJson方法,这样后续你需要把对象序列化回JSON或者XML时,都能保持格式的正确性(XML序列化需要你的字典类型是可序列化的,这个结构完全符合要求)。
这样修改后,你就能正确反序列化所有格式的attributes字段,同时不需要使用dynamic类型,完美支持后续的XML序列化/反序列化需求。
备注:内容来源于stack exchange,提问作者Hub
相关产品推荐
相关产品推荐

