JSON.net:如何在不使用默认构造函数的情况下反序列化?

Posted

技术标签:

【中文标题】JSON.net:如何在不使用默认构造函数的情况下反序列化?【英文标题】:JSON.net: how to deserialize without using the default constructor? 【发布时间】:2014-05-25 21:52:39 【问题描述】:

我有一个类,它有一个默认构造函数,还有一个接受一组参数的重载构造函数。这些参数与对象上的字段匹配,并在构造时分配。在这一点上,我需要默认构造函数用于其他目的,所以如果可以的话,我想保留它。

我的问题:如果我删除默认构造函数并传入 JSON 字符串,对象会正确反序列化并传入构造函数参数而不会出现任何问题。我最终以我期望的方式取回了填充的对象。但是,一旦我将默认构造函数添加到对象中,当我调用 JsonConvert.DeserializeObject<Result>(jsontext) 时,属性将不再填充。

此时我已尝试将new JsonSerializerSettings()CheckAdditionalContent = true 添加到反序列化调用中。那没有任何作用。

另一个注意事项:构造函数参数确实与字段名称完全匹配,只是参数以小写字母开头。我认为这无关紧要,因为就像我提到的那样,反序列化可以在没有默认构造函数的情况下正常工作。

这是我的构造函数示例:

public Result()  

public Result(int? code, string format, Dictionary<string, string> details = null)

    Code = code ?? ERROR_CODE;
    Format = format;

    if (details == null)
        Details = new Dictionary<string, string>();
    else
        Details = details;

【问题讨论】:

也许这可以帮助***.com/questions/8254503/… 复制:unable-to-serialize-classes-with-multiple-constructors-with-json-net 【参考方案1】:

Json.Net 更喜欢在对象上使用默认(无参数)构造函数(如果有的话)。如果有多个构造函数并且您希望 Json.Net 使用非默认的构造函数,那么您可以将 [JsonConstructor] 属性添加到您希望 Json.Net 调用的构造函数中。

[JsonConstructor]
public Result(int? code, string format, Dictionary<string, string> details = null)

    ...

构造函数参数名称必须与 JSON 对象的相应属性名称匹配(忽略大小写)才能正常工作。但是,您不必为对象的每个属性都有一个构造函数参数。对于那些没有被构造函数参数覆盖的 JSON 对象属性,Json.Net 将在构造对象后尝试使用公共属性访问器(或标记为[JsonProperty] 的属性/字段)填充对象。

如果您不想为您的类添加属性或不想控制您尝试反序列化的类的源代码,那么另一种选择是创建自定义JsonConverter 来实例化和填充您的对象。例如:

class ResultConverter : JsonConverter

    public override bool CanConvert(Type objectType)
    
        return (objectType == typeof(Result));
    

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    
        // Load the JSON for the Result into a JObject
        JObject jo = JObject.Load(reader);

        // Read the properties which will be used as constructor parameters
        int? code = (int?)jo["Code"];
        string format = (string)jo["Format"];

        // Construct the Result object using the non-default constructor
        Result result = new Result(code, format);

        // (If anything else needs to be populated on the result object, do that here)

        // Return the result
        return result;
    

    public override bool CanWrite
    
        get  return false; 
    

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    
        throw new NotImplementedException();
    

然后,将转换器添加到您的序列化器设置中,并在反序列化时使用这些设置:

JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new ResultConverter());
Result result = JsonConvert.DeserializeObject<Result>(jsontext, settings);

【讨论】:

这行得通。我现在必须在我的模型项目中使用 JSON.net 依赖项,这有点糟糕,但嘿嘿。我会将此标记为答案。 还有其他选项——您可以为您的班级创建自定义JsonConverter。这将消除依赖关系,但是您必须自己在转换器中处理实例化和填充对象。也可以编写一个自定义的ContractResolver,通过更改其JsonObjectContract 来指示Json.Net 使用另一个构造函数,但这可能比听起来要复杂一些。 是的,我认为该属性可以正常工作。反序列化调用实际上是通用的,因此它可以是任何类型的对象。我认为您的原始答案会很好。感谢您的信息! 如果可以为构造函数选择设置另一个约定,那将非常有帮助。例如,我认为 Unity 容器支持这一点。然后你可以让它总是选择具有大多数参数的构造函数,而不是回退到默认的构造函数。 Json.Net 中是否存在这样的扩展点? 别忘了using Newtonsoft.Json;【参考方案2】:

有点晚了,不完全适合这里,但我要在这里添加我的解决方案,因为my question 已作为此解决方案的副本关闭,并且因为此解决方案完全不同。

我需要一种通用的方法来指示Json.NET 为用户定义的结构类型选择最具体的构造函数,因此我可以省略JsonConstructor 属性,这将为定义每个此类结构的项目添加依赖关系。

我已经进行了一些逆向工程并实现了一个自定义合约解析器,其中我覆盖了 CreateObjectContract 方法以添加我的自定义创建逻辑。

public class CustomContractResolver : DefaultContractResolver 

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    
        var c = base.CreateObjectContract(objectType);
        if (!IsCustomStruct(objectType)) return c;

        IList<ConstructorInfo> list = objectType.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).OrderBy(e => e.GetParameters().Length).ToList();
        var mostSpecific = list.LastOrDefault();
        if (mostSpecific != null)
        
            c.OverrideCreator = CreateParameterizedConstructor(mostSpecific);
            c.CreatorParameters.AddRange(CreateConstructorParameters(mostSpecific, c.Properties));
        

        return c;
    

    protected virtual bool IsCustomStruct(Type objectType)
    
        return objectType.IsValueType && !objectType.IsPrimitive && !objectType.IsEnum && !objectType.Namespace.IsNullOrEmpty() && !objectType.Namespace.StartsWith("System.");
    

    private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
    
        method.ThrowIfNull("method");
        var c = method as ConstructorInfo;
        if (c != null)
            return a => c.Invoke(a);
        return a => method.Invoke(null, a);
    

我就是这样用的。

public struct Test 
  public readonly int A;
  public readonly string B;

  public Test(int a, string b) 
    A = a;
    B = b;
  


var json = JsonConvert.SerializeObject(new Test(1, "Test"), new JsonSerializerSettings 
  ContractResolver = new CustomContractResolver()
);
var t = JsonConvert.DeserializeObject<Test>(json);
t.A.ShouldEqual(1);
t.B.ShouldEqual("Test");

【讨论】:

我目前正在使用上面接受的答案,但也要感谢您展示您的解决方案! 我删除了对结构的限制(检查objectType.IsValueType),这很好用,谢谢! @AlexAngas 是的,一般来说应用此策略确实有意义,谢谢您的反馈。【参考方案3】:

根据这里的一些答案,我写了一个CustomConstructorResolver 用于当前项目,我认为它可能对其他人有所帮助。

它支持以下解析机制,均可配置:

选择一个私有构造函数,这样您就可以定义一个私有构造函数,而无需使用属性对其进行标记。 选择最具体的私有构造函数,这样您就可以有多个重载,而无需使用属性。 选择标有特定名称属性的构造函数 - 类似于默认解析器,但不依赖于 Json.Net 包,因为您需要引用 Newtonsoft.Json.JsonConstructorAttribute
public class CustomConstructorResolver : DefaultContractResolver

    public string ConstructorAttributeName  get; set;  = "JsonConstructorAttribute";
    public bool IgnoreAttributeConstructor  get; set;  = false;
    public bool IgnoreSinglePrivateConstructor  get; set;  = false;
    public bool IgnoreMostSpecificConstructor  get; set;  = false;

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    
        var contract = base.CreateObjectContract(objectType);

        // Use default contract for non-object types.
        if (objectType.IsPrimitive || objectType.IsEnum) return contract;

        // Look for constructor with attribute first, then single private, then most specific.
        var overrideConstructor = 
               (this.IgnoreAttributeConstructor ? null : GetAttributeConstructor(objectType)) 
            ?? (this.IgnoreSinglePrivateConstructor ? null : GetSinglePrivateConstructor(objectType)) 
            ?? (this.IgnoreMostSpecificConstructor ? null : GetMostSpecificConstructor(objectType));

        // Set override constructor if found, otherwise use default contract.
        if (overrideConstructor != null)
        
            SetOverrideCreator(contract, overrideConstructor);
        

        return contract;
    

    private void SetOverrideCreator(JsonObjectContract contract, ConstructorInfo attributeConstructor)
    
        contract.OverrideCreator = CreateParameterizedConstructor(attributeConstructor);
        contract.CreatorParameters.Clear();
        foreach (var constructorParameter in base.CreateConstructorParameters(attributeConstructor, contract.Properties))
        
            contract.CreatorParameters.Add(constructorParameter);
        
    

    private ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
    
        var c = method as ConstructorInfo;
        if (c != null)
            return a => c.Invoke(a);
        return a => method.Invoke(null, a);
    

    protected virtual ConstructorInfo GetAttributeConstructor(Type objectType)
    
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
            .Where(c => c.GetCustomAttributes().Any(a => a.GetType().Name == this.ConstructorAttributeName)).ToList();

        if (constructors.Count == 1) return constructors[0];
        if (constructors.Count > 1)
            throw new JsonException($"Multiple constructors with a this.ConstructorAttributeName.");

        return null;
    

    protected virtual ConstructorInfo GetSinglePrivateConstructor(Type objectType)
    
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);

        return constructors.Length == 1 ? constructors[0] : null;
    

    protected virtual ConstructorInfo GetMostSpecificConstructor(Type objectType)
    
        var constructors = objectType
            .GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
            .OrderBy(e => e.GetParameters().Length);

        var mostSpecific = constructors.LastOrDefault();
        return mostSpecific;
    

这里是以 XML 文档为要点的完整版本:https://gist.github.com/maverickelementalch/80f77f4b6bdce3b434b0f7a1d06baa95

感谢您的反馈。

【讨论】:

很好的解决方案!感谢分享。【参考方案4】:

Newtonsoft.Json 的默认行为是查找 public 构造函数。如果您的默认构造函数仅用于包含类或相同的程序集,您可以将访问级别降低到 protectedinternal 以便 Newtonsoft.Json 将选择您想要的 public 构造函数。

诚然,这种解决方案非常仅限于特定情况。

internal Result()  

public Result(int? code, string format, Dictionary<string, string> details = null)

    Code = code ?? ERROR_CODE;
    Format = format;

    if (details == null)
        Details = new Dictionary<string, string>();
    else
        Details = details;

【讨论】:

一种被忽视但通常非常简单的方法,无需误入 JSON(反)序列化的危险地带。【参考方案5】:

根据 Zoltan 的回答,我创建了一个变体,让您可以根据其签名使用特定的构造函数。

用法

return new JsonSerializerSettings

    ContractResolver = new DynamicObjectResolver(t =>
    
        if (t == typeof(QueueProperties))
            return new Type[]  typeof(string) ;

        return null;
    )
;

这里是实现

using Newtonsoft.Json.Serialization;
using System;
using System.Collections.Concurrent;
using System.Reflection;

namespace ConsoleApp76.Json

    class DynamicObjectResolver : DefaultContractResolver
    
        private readonly Func<Type, Type[]> GetConstructorSignature;
        private readonly ConcurrentDictionary<Type, ConstructorInfo> TypeToConstructorLookup =
            new ConcurrentDictionary<Type, ConstructorInfo>();

        public DynamicObjectResolver(Func<Type, Type[]> getConstructorSignature)
        
            if (getConstructorSignature is null)
                throw new ArgumentNullException(nameof(getConstructorSignature));
            GetConstructorSignature = getConstructorSignature;
        

        protected override JsonObjectContract CreateObjectContract(Type objectType)
        
            var result = base.CreateObjectContract(objectType);
            ConstructorInfo constructor = TypeToConstructorLookup.GetOrAdd(objectType, t => FindConstructorInfo(t));
            if (constructor is null)
                return result;

            result.OverrideCreator = CreateParameterizedConstructor(constructor);
            foreach (var param in CreateConstructorParameters(constructor, result.Properties))
                result.CreatorParameters.Add(param);

            return result;
        

        private ConstructorInfo FindConstructorInfo(Type objectType)
        
            Type[] constructorSignature = GetConstructorSignature(objectType);
            if (constructorSignature is null)
                return null;

            return objectType.GetConstructor(
                bindingAttr:
                    System.Reflection.BindingFlags.Public
                    | System.Reflection.BindingFlags.NonPublic
                    | System.Reflection.BindingFlags.Instance,
                binder: null,
                types: new Type[]  typeof(string) ,
                modifiers: null);
        

        private static ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method)
        
            if (method is null)
                throw new ArgumentNullException(nameof(method));

            var c = method as ConstructorInfo;
            if (c != null)
                return a => c.Invoke(a);
            return a => method.Invoke(null, a);
        
    


【讨论】:

【参考方案6】:

解决方案:

public Response Get(string jsonData) 
    var json = JsonConvert.DeserializeObject<modelname>(jsonData);
    var data = StoredProcedure.procedureName(json.Parameter, json.Parameter, json.Parameter, json.Parameter);
    return data;

型号:

public class modelname 
    public long parameter get; set; 
    public int parameter get; set; 
    public int parameter get; set; 
    public string parameter get; set; 

【讨论】:

以上是关于JSON.net:如何在不使用默认构造函数的情况下反序列化?的主要内容,如果未能解决你的问题,请参考以下文章

如何在构造函数中访问类变量以在不使用 C++ 中的 this 指针的情况下分配它们

我应该如何在不使用 C++ 中的构造函数的情况下将值(不是指针)转换为子类?

WCF反序列化如何在不调用构造函数的情况下实例化对象?

如何禁止在 C++ 中使用默认构造函数?

如何在不将鉴别器传递给构造函数的情况下映射 Enumeration 类?

如何让 Json.Net 在不忽略子属性的情况下从 documentDB 序列化/反序列化动态/通用对象?