如何将存储属性键值对的对象数组JSON反序列化为C#自定义模型
特殊结构JSON的反序列化实现
你的API返回的是扁平化键值对条目数组:每个数组项为「属性名+属性值」的二元组,和Newtonsoft.Json默认要求的「属性名直接作为JSON对象键」的结构不匹配,因此触发类型不兼容的反序列化异常。可通过以下两种成熟方案完成结构映射:
方案1:自定义Newtonsoft.Json转换器(推荐,无侵入业务代码)
该方案利用Newtonsoft的扩展点自定义反序列化逻辑,原有模型的属性定义完全不需要改动,也可以复用到其他同结构的业务模型上。
- 先定义匹配数组内单个元素的临时DTO,用来承接键值对条目:
public class KeyValuePairItem { public string name { get; set; } public int ptr { get; set; } } - 实现通用的数组转对象的自定义转换器,基于反射完成属性匹配赋值:
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Reflection; // 泛型转换器:把键值对条目数组转换为指定类型的实例 public class KeyValueArrayToObjectConverter<T> : JsonConverter where T : new() { // 仅需要支持反序列化,关闭序列化逻辑减少冗余 public override bool CanWrite => false; // 序列化逻辑无需实现 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } // 核心反序列化逻辑 public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // 读取整个JSON数组为可操作的JToken结构 var keyValueItems = JArray.Load(reader); // 实例化目标类型的空对象 var targetInstance = new T(); foreach (var item in keyValueItems) { // 提取数组元素里的属性名和属性值 var propName = item["name"]?.ToString(); var propValue = item["ptr"]; if (string.IsNullOrEmpty(propName)) continue; // 匹配目标类型上的对应公共实例属性,忽略大小写匹配 var targetProp = objectType.GetProperty( propName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (targetProp == null || !targetProp.CanWrite) continue; // 把值转换成属性对应的实际类型,完成赋值 var convertedValue = propValue.ToObject(targetProp.PropertyType); targetProp.SetValue(targetInstance, convertedValue); } return targetInstance; } public override bool CanConvert(Type objectType) { return objectType == typeof(T); } } - 给原有
UserPtr类加上转换器标记,不改动原有属性结构:[JsonConverter(typeof(KeyValueArrayToObjectConverter<UserPtr>))] public class UserPtr { public int my_var1 { get; set; } public int my_var2 { get; set; } public int my_var3 { get; set; } public int my_var4 { get; set; } }
配置完成后,你原有编写的反序列化代码完全不需要调整,即可正常运行:
var userPtr = JsonConvert.DeserializeObject<UserPtr>(strJson);
方案2:手动分步映射(适合无法修改原有模型代码的场景)
如果不能给现有模型添加特性标记、不能修改原有类结构,可以采用两步处理的方式完成映射:
- 先把JSON数组反序列化为键值对列表
var kvList = JsonConvert.DeserializeObject<List<KeyValuePairItem>>(strJson); - 手动遍历列表,把值赋值给目标模型的对应属性:
var userPtr = new UserPtr(); foreach (var kv in kvList) { switch (kv.name) { case nameof(UserPtr.my_var1): userPtr.my_var1 = kv.ptr; break; case nameof(UserPtr.my_var2): userPtr.my_var2 = kv.ptr; break; case nameof(UserPtr.my_var3): userPtr.my_var3 = kv.ptr; break; case nameof(UserPtr.my_var4): userPtr.my_var4 = kv.ptr; break; } }
注意:这种硬编码映射的方式仅适合属性数量少、结构长期固定的场景,属性较多时维护成本会远高于反射自动映射的方案。
常见踩坑说明
- 你提供的示例JSON中包含
//形式的注释,标准JSON规范本身不支持注释,如果反序列化时遇到解析报错,可以在初始化序列化配置时开启宽松解析:var settings = new JsonSerializerSettings { // 忽略无法匹配的成员 MissingMemberHandling = MissingMemberHandling.Ignore, // 跳过元数据属性的特殊处理 MetadataPropertyHandling = MetadataPropertyHandling.Ignore }; var userPtr = JsonConvert.DeserializeObject<UserPtr>(strJson, settings); - 反射匹配属性时,如果目标属性不是公共访问级别,需要调整
BindingFlags的匹配规则,确保能正确读取到属性元数据。 - 如果后续API返回的
ptr字段类型不固定,可以把KeyValuePairItem中的ptr类型改为JToken,反射转换逻辑会自动适配不同的属性类型。
内容的提问来源于stack exchange,提问作者Chakrit
相关产品推荐
相关产品推荐

