Deniz
Deniz

Reputation: 888

Effect of Order value in DataMember attribute during Json (De)Serialization

In WCF, Order values of DataMember attributes seems to be important during the deserialization of data. If the order is different than it means the contract is different. Of course JSON is a collection of unordered value pairs, but should I worry about the order of the fields in the JSON file during deserialization? Is there any effect of ordering (differences between the DataMember Order values and the order of pairs sent in the JSON string) during the deserialization?

For class

[DataContract, Serializable]
public class Person
{
    [DataMember]
    public string Name {get; set;}
    [DataMember]
    public string Surname {get; set;}
}

Both

{
"Name":"Foo",
"Surname":"Bar"
}

and

{
"Surname":"Foo",
"Name":"Bar"
}

should be same and should be deserialized without any problem, right?

As far as I understood, Order is honored only in the serialization process, in which the created JSON string members follow the order defined in the DataMember attribute? Is that correct?

Upvotes: 2

Views: 1788

Answers (1)

dbc
dbc

Reputation: 116731

In general your conclusion about Json.NET is correct. The original JSON proposal states:

An object is an unordered set of name/value pairs.

And the Current IETF JSON standard states:

An object is an unordered collection of zero or more name/value pairs, where a name is a string and a value is a string, number, boolean, null, object, or array.

An object whose names are all unique is interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings.

Json.NET supports these standards. Thus, while it allows for applications to control property order via attributes when serializing (either through [JsonProperty(Order = X)]; or through [DataMember(Order = X)], which works because Json.NET supports data contract attributes), when deserializing property order usually does not matter.

However, there are some exceptions. Those are:

  1. Polymorpmic "$type" properties emitted when TypeNameHandling is enabled must appear first in the object. This limitation, however, can be removed by setting MetadataPropertyHandling.ReadAhead. See here for details.

  2. Custom JsonConverters have access to the incoming token stream so can in theory behave differently depending on the order of properties.

    For instance, when deserializing a DataTable, Json.NET's built-in DataTableConverter will create columns in the order that they are encountered in the JSON file.

    It's also possible to write converters that assume a certain property order and thus are subtly broken. For instance, consider the following:

    public struct Vector2D
    {
        public readonly double X;
        public readonly double Y;
    
        public Vector2D(double x, double y)
        {
            this.X = x;
            this.Y = y;
        }
    }
    
    public class Vector2DConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(Vector2D);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType != JsonToken.StartObject)
                throw new JsonSerializationException();
            reader.Read(); // StartObject
            reader.Read(); // X
            var x = serializer.Deserialize<double>(reader);
            reader.Read(); // Consume value
            reader.Read(); // Y
            var y = serializer.Deserialize<double>(reader);
            reader.Read(); // Consume value
            reader.Read(); // EndObject
            return new Vector2D(x, y);
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            var vec = (Vector2D)value;
            writer.WriteStartObject();
            writer.WritePropertyName("X");
            writer.WriteValue(vec.X);
            writer.WritePropertyName("Y");
            writer.WriteValue(vec.Y);
            writer.WriteEndObject();
        }
    }
    

    The Vector2DConverter assumes that the properties X and Y come in a certain order, rather than checking the property names and deserializing accordingly. While the converter can read what it writes, its order sensitivity fails to fully conform to the JSON standard. Users of such a converter might run into trouble e.g. if the JSON is temporarily cached in a Dictionary<string, object> before final conversion, since the order of elements in the c# dictionary is non-deterministic.

    None of Json.NET's built-in converters should be broken in this manner, but third-party converters might possibly be.

Update: Json.NET vs data contract serialization and member order

By default WCF uses DataContractSerializer for XML and DataContractJsonSerializer for JSON. DataContractSerializer is sensitive to element order when deserializing. This cannot be disabled; see here, here or here.

DataContractJsonSerializer on the other hand respects data member order when serializing but is insensitive to property order when deserializing as is required by the standard - with one exception. If the JSON contains a "type hint" to preserve polymorphic type identity when sending JSON for polymorphic objects over the wire, for instance:

{"__type":"Circle:#MyApp.Shapes","x":50,"y":70,"radius":10}

Then the "__type" property must appear first in the object, as is stated in the docs:

Note that the type hint must appear first in the JSON representation. This is the only case where order of key/value pairs is important in JSON processing.

From experience I also have found that there must be no spacing between the { and the "__type" property name.

Json.NET also supports data contract attributes, as is explained in DataContract and DataMember Attributes. Its behavior with respect to property ordering is the same as DataContractJsonSerializer (although it does use a different format for type hinting) in that it respects the ordering when serializing but is insensitive to ordering when deserializing.

Upvotes: 1

Related Questions