未标记JsonConstructor的只读属性类反序列化问题及替代方案咨询
解决Newtonsoft.Json无法反序列化无默认构造函数只读类的问题
我之前也碰到过这种头疼的情况——手里的类只有带参数的构造函数和只读属性,序列化没问题,但反序列化就报错,还没法改源码加[JsonConstructor]特性。不用慌,咱们可以通过自定义ContractResolver来搞定,完全不用为每个类手动写转换器。
问题根源
Newtonsoft.Json默认会优先找无参构造函数,或者带[JsonConstructor]标记的构造函数,要是都没有,就会抛出你遇到的那个序列化异常。咱们没法改类的源码,就得从Json.NET的序列化规则下手。
核心解决方案:自定义ContractResolver
通过自定义ContractResolver,我们可以告诉Json.NET该如何选择构造函数,不用修改目标类的任何代码,还能全局复用这个规则。
实现自定义构造函数解析器
创建一个继承自DefaultContractResolver的类,重写ResolveConstructor方法,让它自动筛选出和类属性匹配度最高的构造函数(比如参数名对应属性名、参数数量最多的构造函数):
using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; public class PreferredConstructorContractResolver : DefaultContractResolver { protected override ConstructorInfo ResolveConstructor(Type type, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember) { // 获取类的所有公共构造函数 var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); if (constructors.Length == 0) return base.ResolveConstructor(type, contract, member, containerContract, containerMember); // 提取类的公共属性名(统一转小写,避免大小写不匹配问题) var propertyNames = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(p => p.Name.ToLowerInvariant()) .ToList(); // 筛选出参数名全部对应类属性的构造函数 var candidateConstructors = constructors.Where(c => c.GetParameters().All(p => propertyNames.Contains(p.Name.ToLowerInvariant())) ).ToList(); // 优先选择参数数量最多的构造函数(覆盖最全面的属性) if (candidateConstructors.Any()) { return candidateConstructors.OrderByDescending(c => c.GetParameters().Length).First(); } // 没有匹配的构造函数时,回退到Json.NET的默认逻辑 return base.ResolveConstructor(type, contract, member, containerContract, containerMember); } }
应用自定义解析器
在反序列化时,只需要把JsonSerializerSettings的ContractResolver设置为咱们自定义的类即可:
var settings = new JsonSerializerSettings { ContractResolver = new PreferredConstructorContractResolver(), // 可选:忽略JSON中不存在的类属性,避免额外报错 MissingMemberHandling = MissingMemberHandling.Ignore, NullValueHandling = NullValueHandling.Ignore }; // 反序列化示例 var json = "{\"Foo\":\"test-content\",\"Bar\":true}"; var myClassInstance = JsonConvert.DeserializeObject<MyClass>(json, settings);
逻辑说明
这个自定义解析器会按以下步骤工作:
- 遍历目标类的所有公共构造函数
- 筛选出参数名全部对应类中公共属性的构造函数(忽略大小写差异)
- 选择参数数量最多的那个构造函数作为反序列化的入口
- 如果没有匹配的构造函数,自动回退到Json.NET的默认解析逻辑
这样就实现了和添加[JsonConstructor]特性完全等效的效果,还不用碰目标类的源码。
注意事项
- 确保JSON中的属性名和构造函数的参数名尽量对应(代码已经做了大小写忽略处理,不用严格一致)
- 如果有多个构造函数都满足匹配条件,代码会优先选参数最多的那个,你可以根据自己的需求调整优先级逻辑(比如优先选择包含特定参数的构造函数)
内容的提问来源于stack exchange,提问作者Fire Lancer
相关产品推荐
相关产品推荐

