【问题标题】:How does JSON deserialization in C# workC# 中的 JSON 反序列化如何工作
【发布时间】:2017-01-26 09:15:53
【问题描述】:

我试图了解JsonConvert.DeserializeObject<X>(someJsonString) 是如何使用构造函数设置值的。

using Newtonsoft.json

public class X {

    [JsonProperty("some_Property")]
    public string SomeProperty {get;}

    [JsonProperty("some_Property_2")]
    public string SomeProperty2 {get;}

    public X(string someProperty, string someProperty2) {
        SomeProperty = someProperty;
        SomeProperty2 = someProperty2;
    }

    public static X parseObject(string parseThisJson) {
      JsonConvert.DeserializeObject<X>(someJsonString);
    }
}

在上面的代码中,我想了解 JsonConvert.DeserializeObject 是如何正确反序列化它的。 json 序列化是否使用此 public X(string someProperty, string someProperty2) 构造函数?如果是这样,这个构造函数是如何调用和使用的?

如果 parseThisJson 除了 some_Property 和 some_Property_2 之外还有更多的键值对会发生什么?

【问题讨论】:

  • 您可以在 1 分钟内检查。在构造函数中设置断点
  • 不完全是,这取决于序列化/反序列化的实际类型。 XmlSerialization 例如 does 使用默认构造函数。然而,这对这里的实际问题毫无意义。
  • 你知道它是开源的,你可以在github上查看它对吗?
  • 构造函数中未出现的属性值仍将使用 PropertyInfo.SetValue() 设置。 msdn.microsoft.com/en-us/library/…
  • 查看 Avoiding default constructors and public property setters 了解 Json.NET 的作用。基本上,它确实在参数化构造函数是唯一要调用的构造函数时使用它,或者它被标记为[JsonConstructorAttribute],并按名称模大小写匹配JSON属性的参数。

标签: c# json.net


【解决方案1】:

在深入了解Newtonsoft.Json 来源之后,我可以告诉您那里使用的对象实例化算法。是的,构造函数几乎总是被称为 (*)。问题只是“哪一个?”。这是答案的多彩版本:

TL;DR 首先,Newtonsoft.Json 创建您要反序列化的类型的JsonContract。它的抽象类。它对字典、数组、对象等有不同的实现。在你的情况下,JsonObjectContract 将被创建。合约包含关于反序列化类型的各种元数据。对我们来说最有趣的是:

  • IsInstantiable - 定义反序列化类型是否可实例化(见下文)
  • Properties - 它的对象属性集合
  • DefaultCreator - 用于创建对象的默认创建方法Func&lt;object&gt;
  • DefaultCreatorNonPublic - 定义默认构造函数是否为非公开的
  • OverrideCreator - 非默认创建者,在 JsonConstructorAttribute 应用于对象的构造函数时使用
  • ParametrizedCreator - 调用参数化构造函数的创建者,如果我们既没有默认创建者也没有覆盖创建者,则使用它
  • CreatorParameters - 用于覆盖创建者或参数化创建者的属性集合
  • MemberSerialization - 此值定义属性和字段的序列化方式。默认情况下,它设置为OptOut - 即所有公共成员都被序列化。如果你想排除一些,你应该使用JsonIgnore 属性。但也有Fields 选项,它表示所有公共 和私有 字段都应该被序列化。有几个可以打开此选项。但默认情况下它是禁用的。

可以通过反映类型元数据来检索其中一些元数据。例如。 IsInstantiable 是通过检查反序列化的类型是否不是抽象而不是接口来计算的。一些元数据由DefaultContractResolver 添加。特别是,它定义了对象的构造方式。在伪代码中:

if (contract.IsInstantiable)
{
   if (type has default constructor or its a value type)
   {
       contract.DefaultCreator = get default (parameterless) constructor;
       contract.DefaultCreatorNonPublic = check if default constructor public
   }

   if (we have constructor marked with JsonConstructorAttribute)
   {
       contract.OverrideCreator = constructor marked with attribute
       contract.CreatorParameters = get properties which match constructor parameters
   }
   else if (contract.MemberSerialization == MemberSerialization.Fields)
   {
       // only if the upplication if fully trusted
       contract.DefaultCreator = FormatterServices.GetUninitializedObject 
   }
   else if (contract.DefaultCreator == null || contract.DefaultCreatorNonPublic)
   {
         if (we have one public constructor with parameters)
         {
              contract.ParametrizedCreator = constructor with parameters;
              contract.CreatorParameters = get properties which match ctor parameters
         }
   }
}

所以,正如您所见,优先级转到标有JsonConstructorAttribute 属性的构造函数。如果有多个这样的构造函数,你也会得到错误。

(*) 接下来是唯一可以在不调用构造函数的情况下创建对象的情况。例如。如果您将使用[JsonObject(MemberSerialization = MemberSerialization.Fields)] 属性标记类以序列化私有字段。

然后我们检查我们是否有默认的非私有无参数构造函数。如果是这样,那么我们选择其他构造函数 - 一个具有参数并且应该是公共的。如果这样的构造函数不止一个,也会报错。

最后要注意的是 - CreatorParameters。 Newtonsoft.Json 使用反射来获取构造函数参数,然后尝试通过这些构造函数参数的名称来查找与对象属性最接近的匹配项。它还检查要匹配的属性和参数的类型。如果未找到匹配项,则将默认值传递给此参数化构造函数。

【讨论】:

  • 感谢您的精彩解释。我有一个问题,您说 Newtonsoft.Json 使用反射来获取构造函数参数,然后尝试通过这些构造函数参数的名称与对象的属性找到最接近的匹配,您所说的 是什么意思按名称最接近的匹配 属性名称是否不需要 100% 匹配?例如 SomeProprty 将被视为与 SomeProperty 匹配?
  • @user2358262 它首先尝试获取具有 100% 匹配名称的属性,但如果失败,则尝试查找忽略名称大小写的属性。我相信这是由于不同的命名约定。对于属性,我们使用 PascalCase 命名,对于参数 camelCase。您可以查看此代码here
猜你喜欢
  • 2021-09-24
  • 2011-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-24
  • 1970-01-01
  • 2014-01-06
相关资源
最近更新 更多