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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 21:01:02