Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Newtonsoft JSON.net deserialization error where fields in JSON change order

Tags:

c#

json.net

This is a WCF service getting requests from android devices. Same request works from Lollipop devices, not from jellybean devices, because jellybean arranges the JSON differently on creation.

The exception:

Unexpected token when deserializing object: String. Path 'SearchFilters.config.$type', line 1, position 212.

Non working Json:

{
    "DeviceType": 2,
    "SearchFilters": {
        "config": {
            "$values": [
                {
                    "Collection": {
                        "DeviceType": 2
                    },
                    "Category": ""
                }
            ],
            "$type": "System.Collections.Generic.List`1[[Yoosh.SharedClasses.YooshConfig, YooshSharedClassesDll]], mscorlib"
        }
    },
    "RequestingUserId": "66666666-6666-6666-6666-666666666666",
    "APIKey": "xxx"
}

Working Json:

{
    "APIKey": "xxx",
    "DeviceType": 2,
    "RequestingUserId": "66666666-6666-6666-6666-666666666666",
    "SearchFilters": {
        "config": {
            "$type": "System.Collections.Generic.List`1[[Yoosh.SharedClasses.YooshConfig, YooshSharedClassesDll]], mscorlib",
            "$values": [
                {
                    "Category": "",
                    "Collection": {
                        "DeviceType": 2
                    }
                }
            ]
        }
    }
}

Some fields are in a different order.. Thats the only difference.

The C# class:

public class QueryParameters 
{
    BaseParameters m_baseParameters;
    Guid m_gRequestingUserId;
    Dictionary<string, object> m_SearchFilters;

    [DataMember]
    public  string APIKey
    {
        get { return m_baseParameters.APIKey; }
        set { m_baseParameters.APIKey = value; }
    }

    [DataMember]
    public  BaseParameters.YooshDeviceType DeviceType
    {
        get { return m_baseParameters.DeviceType; }
        set { m_baseParameters.DeviceType = value; }
    }

    [DataMember]
    public  string DeviceId
    {
        get { return m_baseParameters.DeviceId; }
        set { m_baseParameters.DeviceId = value; }
    }

    [DataMember]
    public Guid RequestingUserId
    {
        get { return m_gRequestingUserId; }
        set { m_gRequestingUserId = value; }
    }

    [DataMember]
    public Dictionary<string, object> SearchFilters
    {
       get { return m_SearchFilters; }
        set { m_SearchFilters = value; }
    }
}

Json.net version: 6.0.8

like image 325
Shy Peleg Avatar asked Apr 07 '15 13:04

Shy Peleg


1 Answers

Set JsonSerializerSettings.MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead.

According to the documentation:

This sample deserializes JSON with MetadataPropertyHandling set to ReadAhead so that metadata properties do not need to be at the start of an object.

 string json = @"{
  'Name': 'James',
  'Password': 'Password1',
  '$type': 'MyNamespace.User, MyAssembly'
}";

object o = JsonConvert.DeserializeObject(json, new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    // $type no longer needs to be first
    MetadataPropertyHandling = MetadataPropertyHandling.ReadAhead
});

Note that this setting will impact performance.

Finally, when using TypeNameHandling, do take note of this caution from the Newtonsoft docs:

TypeNameHandling should be used with caution when your application deserializes JSON from an external source. Incoming types should be validated with a custom SerializationBinder when deserializing with a value other than None.

For a discussion of why this may be necessary, see TypeNameHandling caution in Newtonsoft Json.

like image 194
dbc Avatar answered Sep 21 '22 16:09

dbc