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

未标记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);

逻辑说明

这个自定义解析器会按以下步骤工作:

  1. 遍历目标类的所有公共构造函数
  2. 筛选出参数名全部对应类中公共属性的构造函数(忽略大小写差异)
  3. 选择参数数量最多的那个构造函数作为反序列化的入口
  4. 如果没有匹配的构造函数,自动回退到Json.NET的默认解析逻辑

这样就实现了和添加[JsonConstructor]特性完全等效的效果,还不用碰目标类的源码。

注意事项

  • 确保JSON中的属性名和构造函数的参数名尽量对应(代码已经做了大小写忽略处理,不用严格一致)
  • 如果有多个构造函数都满足匹配条件,代码会优先选参数最多的那个,你可以根据自己的需求调整优先级逻辑(比如优先选择包含特定参数的构造函数)

内容的提问来源于stack exchange,提问作者Fire Lancer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 21:32:42