Newtonsoft.Json反序列化如何按属性类型选择对应构造函数
问题根因
Newtonsoft.Json默认反序列化时,会在合同初始化阶段就选定类型要使用的构造函数,这个阶段还没有读取待反序列化的JSON内容,碰到多个参数个数相同、类型兼容的构造函数时就会抛出歧义异常,无法自动根据JSON实际包含的属性选择对应构造函数。
实现方案
不需要修改原类型代码,也不需要编写全量自定义JsonConverter,只需要做两处轻量扩展即可实现需求:
1. 自定义ContractResolver收集候选构造函数
继承默认的DefaultContractResolver,针对目标泛型类型收集所有可用的单参数构造函数,不在初始化阶段固定构造函数选择:
using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using System.Reflection; public class ConstructorAutoSelectResolver : DefaultContractResolver { protected override JsonObjectContract CreateObjectContract(Type objectType) { var contract = base.CreateObjectContract(objectType); // 仅对目标Test<,>泛型类型做特殊处理,其余类型走默认逻辑 if (objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Test<,>)) { var constructors = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance); // 暂存所有候选构造函数,供后续读取JSON后选择 contract.ExtensionData["CandidateConstructors"] = constructors; // 临时赋值第一个构造函数作为占位,避免合同初始化阶段抛出无可用构造函数的错误 contract.OverrideConstructor = constructors.First(); } return contract; } }
2. 轻量JsonConverter实现构造函数动态匹配
只需要实现构造函数选择的逻辑,剩余的属性填充、值转换等操作全部交给Newtonsoft.Json默认逻辑处理,不需要手动编写属性赋值代码:
public class ConstructorMatchConverter : JsonConverter { public override bool CanConvert(Type objectType) { return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Test<,>); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // 读取当前JSON对象,获取实际存在的属性 var jObj = JObject.Load(reader); var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(objectType); var candidates = (ConstructorInfo[])contract.ExtensionData["CandidateConstructors"]; ConstructorInfo targetConstructor = null; object[] constructorArgs = null; // 遍历候选构造函数,匹配参数与JSON属性 foreach (var ctor in candidates) { var parameter = ctor.GetParameters().First(); // 查找JSON中与构造函数参数匹配的属性:名称兼容、值可转换为参数类型 var matchedProperty = jObj.Properties().FirstOrDefault(prop => { if (prop.Value.Type is JTokenType.Null or JTokenType.Undefined) return false; // 名称匹配默认支持大小写不敏感,若用了自定义命名策略可替换为从contract.Properties查找映射名 return prop.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase); }); if (matchedProperty == null) continue; try { var argValue = matchedProperty.Value.ToObject(parameter.ParameterType, serializer); targetConstructor = ctor; constructorArgs = new[] { argValue }; break; } catch { // 值类型不兼容则跳过当前构造函数,继续匹配下一个 } } if (targetConstructor == null) throw new JsonSerializationException($"未找到适配的构造函数反序列化类型 {objectType.FullName}"); // 调用匹配到的构造函数创建实例 var instance = targetConstructor.Invoke(constructorArgs); // 剩余属性填充完全走序列化器默认逻辑,兼容所有原有序列化配置 serializer.Populate(jObj.CreateReader(), instance); return instance; } // 序列化逻辑完全走默认实现,不需要自定义 public override bool CanWrite => false; public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotSupportedException(); }
使用方式
初始化序列化配置时注册上述两个扩展即可:
var jsonSettings = new JsonSerializerSettings { ContractResolver = new ConstructorAutoSelectResolver(), Converters = { new ConstructorMatchConverter() } // 其余原有配置(命名策略、空值处理等)可正常保留,不会受影响 }; // 反序列化时会自动匹配构造函数 // 传入 {"FirstProperty":"demo"} 时自动调用 Test(T firstProperty) 构造函数 // 传入 {"SecondProperty":123} 时自动调用 Test(U secondProperty) 构造函数 var instance1 = JsonConvert.DeserializeObject<Test<string, int>>("{\"FirstProperty\":\"testValue\"}", jsonSettings); var instance2 = JsonConvert.DeserializeObject<Test<string, int>>("{\"SecondProperty\":999}", jsonSettings);
如果你的序列化配置使用了自定义命名策略(如驼峰命名、蛇形命名),或者属性通过
[JsonProperty]标记了自定义别名,可以在匹配属性名的环节,从contract.Properties集合中查找参数名对应的序列化属性名后再做对比,避免匹配失败。
内容的提问来源于stack exchange,提问作者MiBuena
相关产品推荐
相关产品推荐

