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

198

我有一个类,它有一个默认构造函数和一个重载构造函数,接受一组参数。这些参数与对象上的字段相匹配,并在构造时进行赋值。此时我需要默认构造函数用于其他目的,因此如果可能的话,我想保留它。

我的问题是:如果我删除默认构造函数并传入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;
}

也许这可以帮助:https://dev59.com/Bmsy5IYBdhLWcg3w4x_c - csharpwinphonexaml
@nawfal(以及其他好奇的人...)该问题已被标记为此当前[和稍微旧一些的]问题的重复。 - ruffin
6个回答

303

如果对象有默认(无参数)构造函数,则Json.Net会优先使用它。如果有多个构造函数并且您想让Json.Net使用非默认构造函数,则可以在要调用的构造函数上添加[JsonConstructor]属性。

[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);

6
这个方法可行。虽然现在需要在我的模型项目中添加 JSON.net 依赖,但有什么办法呢。我会将其标记为答案。 - kmacdonald
5
还有其他选项——你可以为你的类创建一个自定义的JsonConverter,这将消除依赖关系,但需要在转换器中自己处理实例化和填充对象的过程。也可能编写一个自定义的ContractResolver,通过更改其JsonObjectContract来指示Json.Net使用另一个构造函数,但这可能比听起来要棘手一些。 - Brian Rogers
4
如果可以为构造函数选择设置另一种约定,那将非常有帮助。例如,我认为Unity容器支持此功能。然后,您可以使其始终选择具有最多参数的构造函数,而不是返回默认构造函数。在Json.Net中是否存在这样的扩展点? - julealgon
我采用了自定义JsonConverter的第二个建议,它完美地解决了我的问题。在我的情况下,一个第三方SDK从我正在序列化/反序列化的类中删除了无参数构造函数,因此突然间属性为空。 - Dan Harris
1
不要忘记添加 using Newtonsoft.Json; - Bruno Bieri
显示剩余3条评论

49
有点晚了,而且不太适合这里,但我会在这里添加我的解决方案,因为我的问题已被关闭为此类问题的副本,并且因为这个解决方案完全不同。 我需要一种通用的方法来指示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");

4
我目前正在使用上面被接受的答案,但还想感谢你展示自己的解决方案! - DotBert
2
我已经移除了结构体的限制(即检查 objectType.IsValueType),现在一切都很好,谢谢! - Alex Angas
@AlexAngas 是的,通常情况下采用这种策略确实是有意义的。谢谢你的反馈。 - Zoltán Tamási
1
“我的问题”链接似乎会直接回到这里。 https://dev59.com/DWAg5IYBdhLWcg3w7erx#35865022 - 这是与此相同的问题ID。 为了上下文,你的在哪里? - ruffin

5

根据这里的一些答案,我编写了一个 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文档的完整版本,以gist形式呈现:https://gist.github.com/bjorn-jarisch/80f77f4b6bdce3b434b0f7a1d06baa95。欢迎提出反馈意见。

很棒的解决方案!感谢分享。 - Tho Mai
1
@ruffin 谢谢你提醒,我已经修复了链接。 - Björn Jarisch

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(反)序列化的危险领域的情况下实现所需的功能。 - Ian Kemp

0
基于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; }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接