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


136

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

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

在这一点上,我已经尝试添加new JsonSerializerSettings(){CheckAdditionalContent = true}反序列化调用。那什么也没做。

另一个说明。除了参数以小写字母开头外,contructor参数确实与字段名称完全匹配。我认为这并不重要,因为就像我提到的那样,反序列化可以在没有默认构造函数的情况下正常工作。

这是我的构造函数的一个示例:

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;
}


Answers:


208

如果有一个对象,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);

4
这工作了。我现在不得不在我的模型项目中采用JSON.net依赖,这有点糟,但是嘿。我将其标记为答案。
kmacdonald 2014年

3
还有其他选项-您可以JsonConverter为您的班级创建自定义。这将消除依赖关系,但随后您必须自己在转换器中处理实例化和填充对象。ContractResolver通过更改Json.Net,可以编写一个指示Json.Net使用其他构造函数的自定义JsonObjectContract,但这可能听起来有些棘手。
Brian Rogers

是的,我认为该属性可以正常工作。反序列化调用实际上是通用的,因此它可以是任何类型的对象。我认为您的原始答案会很好。谢谢(你的)信息!
kmacdonald 2014年

2
如果可以为构造函数选择设置另一个约定,那将真的有帮助。例如,我认为Unity容器支持这一点。然后,您可以使它始终选择具有最多参数的构造函数,而不是退回到默认参数。Json.Net是否存在这种扩展点的可能性?
julealgon

1
别忘了using Newtonsoft.Json;
Bruno Bieri

36

有点晚了,并不完全适合这里,但是我要在这里添加我的解决方案,因为我的问题已经作为该问题的副本被关闭,并且因为该解决方案完全不同。

我需要一种通用的方法来指示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");

2
我目前正在使用上面接受的答案,但也感谢您展示您的解决方案!
DotBert '16

1
我删除了对结构的限制(检查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

反馈表示赞赏。


很好的解决方案!感谢分享。
托迈

1

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;
}

0

解:

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; }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.