I have a web service built with ASP.Net, which until now only used XML for its input and output. Now it needs to also be able to work with JSON.
We use xsd2code++ to generate the model from a XSD, with the option to create "IsSpecified" properties enabled (i.e. if a property is specified in a XML, its respective "Specified" property will be true
).
From a XSD like this...
<?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++ creates a class, with properties like this:
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;
}
}
}
This works great for XML.
For example, if ID isn't specified in the input XML, the property IDSpecified will be false
. We can use these "Specified" properties in the business logic layer, so we know what data has to be inserted/updated, and what can be ignored/skipped.
Then, we tried to add JSON serialization. We added a Json formatter to the WebApiConfig class:
config.Formatters.Add(new JsonMediaTypeFormatter());
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver();
The API will now recognize JSON inputs, but the "Specified" properties don't work for complex objects as they do for XML, and will always say they're 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
}
}
Is Newtonsoft's DefaultContractResolver not fully compatible with these "Specified" fields, like XML is? Am I expected to explicitly state for each property if its "Specified" value is true? Or am I missing something?
EDIT: I've uploaded some sample code to GitHub: https://github.com/AndreNobrega/XML-JSON-Serialization-POC
The request bodies I've tried sending can be found in the Examples folder of the project. POST requests can be sent to .../api/Person.
When sending the XML example, I set the Content-Type
header to application/xml
. When sending the JSON example, I set it to application/json
.
If you set a breakpoint in the Post() method of the PersonController class, you will see that xxxSpecified
members for XML requests are set correctly, but not for JSON.
Maybe it's got something to do with the Person.Designer class, that is auto-generated by xsd2code++? Is there a JSON equivalent for the attribute [System.Xml.Serialization.XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
?
Specified
property convention out of the box, as shown here. What is the actual JSON you are deserializing? Does it match the shape of your classes? – DiaJsonMediaTypeFormatter.ReadFromStream()
to deserialize, see dotnetfiddle.net/zWBnxW. Might you please edit your question to share a minimal reproducible example? – ThermodynamicsxxxIsSpecified
pattern is definitely supported by Json.NET, see e.g. How to force Newtonsoft Json to serialize all properties? (Strange behavior with “Specified” property) and XSD.EXE + JSON.NET - How to deal with xxxSpecified generated members? for example questions. – ThermodynamicsDefaultContractResolver.IgnoreIsSpecifiedMembers
somewhere. – ThermodynamicsDetails
in thePerson
constructor? If you do, Json.NET will populate the pre-existing instance and not set back a new instance, and so it seemsDetailsSpecified
never gets set. See dotnetfiddle.net/0taaIn. For comparisonXmlSerializer
never populates existing instances of non-collection types. – ThermodynamicsDefaultContractResolver.IgnoreIsSpecifiedMembers
, but as it's to be expected, the formatter then returns allxxxSpecified
as false. When xsd2code++ generates the classes, it does initializeAddress
andPersonalDetails
(see /Models/Person.Designer.cs). – Enswathe