如何使 Json.NET 为具有复杂值的属性设置 IsSpecified 属性?
How to make Json.NET set IsSpecified properties for properties with complex values?
我有一个使用 ASP.Net 构建的 Web 服务,直到现在它的输入和输出只使用 XML。现在它还需要能够与 JSON.
一起工作
我们使用 xsd2code++ 从 XSD 生成模型,并启用创建 "IsSpecified" properties 的选项(即如果 属性 在 XML 中指定,其各自的“指定”属性 将是 true
).
来自这样的XSD...
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Person">
<xs:complexType>
<xs:sequence>
<xs:element name="ID" type="xs:string"/>
<xs:element name="Details" type="PersonalDetails"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="PersonalDetails">
<xs:sequence>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
... xsd2code++ 创建一个 class,具有如下属性:
public partial class Person
{
#region Private fields
private string _id;
private PersonalDetails _details;
private Address _address;
private bool _iDSpecified;
private bool _detailsSpecified;
private bool _addressSpecified;
#endregion
public Person()
{
this._address = new Address();
this._details = new PersonalDetails();
}
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string ID
{
get
{
return this._id;
}
set
{
this._id = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public PersonalDetails Details
{
get
{
return this._details;
}
set
{
this._details = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public Address Address
{
get
{
return this._address;
}
set
{
this._address = value;
}
}
[XmlIgnore()]
public bool IDSpecified
{
get
{
return this._iDSpecified;
}
set
{
this._iDSpecified = value;
}
}
[XmlIgnore()]
public bool DetailsSpecified
{
get
{
return this._detailsSpecified;
}
set
{
this._detailsSpecified = value;
}
}
[XmlIgnore()]
public bool AddressSpecified
{
get
{
return this._addressSpecified;
}
set
{
this._addressSpecified = value;
}
}
}
这对 XML 非常有用。
例如,如果在输入 XML 中未指定 ID,则 属性 IDSpecified 将为 false
。我们可以在业务逻辑层使用这些“指定”的属性,这样我们就知道哪些数据必须是inserted/updated,哪些可以是ignored/skipped.
然后,我们尝试添加JSON序列化。
我们向 WebApiConfig class:
添加了一个 Json 格式化程序
config.Formatters.Add(new JsonMediaTypeFormatter());
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
API 现在可以识别 JSON 输入,但“指定”属性不适用于复杂的 objects,因为它们对 XML 有效,并且会总是说他们是 false
.
{
"ID": "abc123", // IDSpecified comes through as "true"
"Details": { // DetailsSpecified always comes through as "false"
"FirstName": "John", // FirstNameSpecified = true
"LastName": "Doe", // LastNameSpecified = true
"BirthDate": "1990-06-20" // BirthDateSpecified = true
}
}
Newtonsoft 的 DefaultContractResolver 是否与这些“指定”字段不完全兼容,就像 XML 一样?如果每个 属性 的“指定”值为真,我是否应该明确说明?
还是我遗漏了什么?
编辑:
我已经将一些示例代码上传到 GitHub:https://github.com/AndreNobrega/XML-JSON-Serialization-POC
我尝试发送的请求正文可以在项目的示例文件夹中找到。 POST 可以将请求发送至.../api/Person。
在发送 XML 示例时,我将 Content-Type
header 设置为 application/xml
。发送JSON例子时,我设置为application/json
.
如果您在 PersonController class 的 Post() 方法中设置断点,您将看到 XML 请求的 xxxSpecified
成员设置正确,但不适用于 JSON.
也许与Person.Designer class,即xsd2code++的auto-generated有关?属性 [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
是否有 JSON 等价物?
您似乎遇到了 Json.NET 对 {propertyName}Specified
members: the {propertyName}Specified
property is not set when populating an instance of a preallocated reference type property. As a workaround, you can deserialize with the setting JsonSerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace
的支持的限制。如果这样做,引用类型属性的新实例将由序列化程序创建并在创建后返回,从而切换相应的 {propertyName}Specified
属性.
详细说明如下。在您的 Person
类型中,您会在默认构造函数中自动分配子属性 Address
和 Details
的实例:
public Person()
{
this._address = new Address();
this._details = new PersonalDetails();
}
现在,因为 Json.NET 支持 populating an existing object,在反序列化期间,在调用默认 Person()
构造函数后,它将填充 Address
和 [=18= 的值] 你构建的,而不是创建新的。正因为如此,它显然从不为 Address
和 Details
调用 setter,也许是因为 Newtonsoft 认为没有必要这样做。但是,这反过来又似乎阻止了相应的 Specified
属性的设置,因为看起来 Json.NET 仅在调用 setter 时才切换它们。
(相比之下,XmlSerializer
从不填充集合值属性以外的预分配引用类型属性,因此 XmlSerializer
不应出现这种情况。)
这可能是 Json.NET 对 {propertyName}Specified
模式的实现中的错误。您可能想 open an issue 使用 Newtonsoft 了解它。
演示 fiddle #1 here.
作为解决方法,您可以:
反序列化设置 JsonSerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace
像这样:
config.Formatters.JsonFormatter.SerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace;
此选项将始终创建新对象,从而触发 Specified
属性的设置。
演示 fiddle #2 here.
从 Person
的默认构造函数中删除 Address
和 Details
的分配。不是很推荐,但确实能解决问题。
演示 fiddle #3 here.
我有一个使用 ASP.Net 构建的 Web 服务,直到现在它的输入和输出只使用 XML。现在它还需要能够与 JSON.
一起工作我们使用 xsd2code++ 从 XSD 生成模型,并启用创建 "IsSpecified" properties 的选项(即如果 属性 在 XML 中指定,其各自的“指定”属性 将是 true
).
来自这样的XSD...
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Person">
<xs:complexType>
<xs:sequence>
<xs:element name="ID" type="xs:string"/>
<xs:element name="Details" type="PersonalDetails"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:complexType name="PersonalDetails">
<xs:sequence>
<xs:element name="FirstName" type="xs:string"/>
<xs:element name="LastName" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
... xsd2code++ 创建一个 class,具有如下属性:
public partial class Person
{
#region Private fields
private string _id;
private PersonalDetails _details;
private Address _address;
private bool _iDSpecified;
private bool _detailsSpecified;
private bool _addressSpecified;
#endregion
public Person()
{
this._address = new Address();
this._details = new PersonalDetails();
}
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string ID
{
get
{
return this._id;
}
set
{
this._id = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public PersonalDetails Details
{
get
{
return this._details;
}
set
{
this._details = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public Address Address
{
get
{
return this._address;
}
set
{
this._address = value;
}
}
[XmlIgnore()]
public bool IDSpecified
{
get
{
return this._iDSpecified;
}
set
{
this._iDSpecified = value;
}
}
[XmlIgnore()]
public bool DetailsSpecified
{
get
{
return this._detailsSpecified;
}
set
{
this._detailsSpecified = value;
}
}
[XmlIgnore()]
public bool AddressSpecified
{
get
{
return this._addressSpecified;
}
set
{
this._addressSpecified = value;
}
}
}
这对 XML 非常有用。
例如,如果在输入 XML 中未指定 ID,则 属性 IDSpecified 将为 false
。我们可以在业务逻辑层使用这些“指定”的属性,这样我们就知道哪些数据必须是inserted/updated,哪些可以是ignored/skipped.
然后,我们尝试添加JSON序列化。 我们向 WebApiConfig class:
添加了一个 Json 格式化程序config.Formatters.Add(new JsonMediaTypeFormatter());
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
API 现在可以识别 JSON 输入,但“指定”属性不适用于复杂的 objects,因为它们对 XML 有效,并且会总是说他们是 false
.
{
"ID": "abc123", // IDSpecified comes through as "true"
"Details": { // DetailsSpecified always comes through as "false"
"FirstName": "John", // FirstNameSpecified = true
"LastName": "Doe", // LastNameSpecified = true
"BirthDate": "1990-06-20" // BirthDateSpecified = true
}
}
Newtonsoft 的 DefaultContractResolver 是否与这些“指定”字段不完全兼容,就像 XML 一样?如果每个 属性 的“指定”值为真,我是否应该明确说明? 还是我遗漏了什么?
编辑: 我已经将一些示例代码上传到 GitHub:https://github.com/AndreNobrega/XML-JSON-Serialization-POC
我尝试发送的请求正文可以在项目的示例文件夹中找到。 POST 可以将请求发送至.../api/Person。
在发送 XML 示例时,我将 Content-Type
header 设置为 application/xml
。发送JSON例子时,我设置为application/json
.
如果您在 PersonController class 的 Post() 方法中设置断点,您将看到 XML 请求的 xxxSpecified
成员设置正确,但不适用于 JSON.
也许与Person.Designer class,即xsd2code++的auto-generated有关?属性 [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
是否有 JSON 等价物?
您似乎遇到了 Json.NET 对 {propertyName}Specified
members: the {propertyName}Specified
property is not set when populating an instance of a preallocated reference type property. As a workaround, you can deserialize with the setting JsonSerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace
的支持的限制。如果这样做,引用类型属性的新实例将由序列化程序创建并在创建后返回,从而切换相应的 {propertyName}Specified
属性.
详细说明如下。在您的 Person
类型中,您会在默认构造函数中自动分配子属性 Address
和 Details
的实例:
public Person()
{
this._address = new Address();
this._details = new PersonalDetails();
}
现在,因为 Json.NET 支持 populating an existing object,在反序列化期间,在调用默认 Person()
构造函数后,它将填充 Address
和 [=18= 的值] 你构建的,而不是创建新的。正因为如此,它显然从不为 Address
和 Details
调用 setter,也许是因为 Newtonsoft 认为没有必要这样做。但是,这反过来又似乎阻止了相应的 Specified
属性的设置,因为看起来 Json.NET 仅在调用 setter 时才切换它们。
(相比之下,XmlSerializer
从不填充集合值属性以外的预分配引用类型属性,因此 XmlSerializer
不应出现这种情况。)
这可能是 Json.NET 对 {propertyName}Specified
模式的实现中的错误。您可能想 open an issue 使用 Newtonsoft 了解它。
演示 fiddle #1 here.
作为解决方法,您可以:
反序列化设置
JsonSerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace
像这样:config.Formatters.JsonFormatter.SerializerSettings.ObjectCreationHandling = ObjectCreationHandling.Replace;
此选项将始终创建新对象,从而触发
Specified
属性的设置。演示 fiddle #2 here.
从
Person
的默认构造函数中删除Address
和Details
的分配。不是很推荐,但确实能解决问题。演示 fiddle #3 here.