2

I am unable to deserialize JSON data on my client end point. It receives JSON data like this:

{
    "WaitForClientMessagesResult": [
        {
            "__type": "KeepAliveMessage:#Data.WebGateway",
            "MessageId": 1,
            "Type": 0,
            "PositionInQueue": -1
        }
    ]
}

KeepAliveMessage is a derived class of WebResponseMessage. The service returns an IEnumerable<WebResponseMessage>.

I'm getting exceptions like this:

Newtonsoft.Json.JsonSerializationException:

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.IEnumerable`1[ Red5Prototype.Models.WaitForClientMessagesResult]

because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.

I've tried calling the deserialization many ways:

  • WaitForClientMessagesResult deserialized = JsonConvert.DeserializeObject<WaitForClientMessagesResult>(keepAliveResult);
  • WaitForClientMessagesResult[] deserialized = JsonConvert.DeserializeObject<WaitForClientMessagesResult[]>(keepAliveResult);
  • IEnumerable<WebClientMessage> deserialized = JsonConvert.DeserializeObject<IEnumerable<WebClientMessage>>(keepAliveResult);

None if these work.

I'm not sure how to structure my classes on the client end to use a Json deserializer.

edit: My base class is defined like this:

[KnownType(typeof(KeepAliveMessage))]    
[DataContract]
public abstract class WebClientMessage
{
    public WebClientMessage() { }

    [DataMember]
    public int MessageId { get; set; }
    [DataMember]
    public WebClientMessageType Type { get; set; }
}

with Keepalive like this:

[DataContract]
public class KeepAliveMessage : WebClientMessage
{
    public KeepAliveMessage() { }

    [DataMember]
    public int PositionInQueue { get; set; }
}

I tried making WebClientMessage a member of WaitForClientMessagesResult

[DataContract]
public class WaitForClientMessagesResult
{
    public WaitForClientMessagesResult() {}

    [DataMember]
    WebClientMessage [] Messages;
}

That didn't work either.

Alex
  • 13,024
  • 33
  • 62
tatmanblue
  • 1,176
  • 2
  • 14
  • 30
  • Show us how `WaitForClientMessagesResult` is declared. – Yuval Itzchakov Apr 28 '15 at 20:45
  • Do you need to use the `__type` property? If so this gets a little more complicated – Andrew Whitaker Apr 28 '15 at 21:11
  • the __type field comes from the webservice, which I cannot change. It is using .NET serialization. – tatmanblue Apr 28 '15 at 21:32
  • The `"__type"` property is how `DataContractJsonSerializer` formats polymorphic type information. Json.NET uses a completely different format, [which is hardcoded](https://json.codeplex.com/workitem/22429). You might want to consider switching to `DataContractJsonSerializer` since it appears the server is using it to send the data. – dbc Apr 28 '15 at 22:17
  • If you need to stay with Json.NET, perhaps this solution works: https://stackoverflow.com/questions/9490345/json-net-change-type-field-to-another-name/29834480#29834480 – dbc Apr 28 '15 at 22:19
  • Dictionary deserialzed = JsonConvert.DeserializeObject>(keepAliveData); gets me a little bit of the way. The dictionary contains 1 element. The Value is a JArray. The value appears to be the inner json (looking at it in Visual Studio debugging) If I can figure out how to deserialize that, I'm good. – tatmanblue Apr 28 '15 at 22:58

2 Answers2

0

There are two issues here. Firstly, the JSON root object has an array-valued property named WaitForClientMessagesResult not Messages so you need to do something like this:

[DataContract(Name = "WaitForClientMessagesResult", Namespace = "http://schemas.datacontract.org/2004/07/Data.WebGateway")]
public class WaitForClientMessagesResult
{
    public WaitForClientMessagesResult() { }

    [DataMember(Name = "WaitForClientMessagesResult")]
    public WebClientMessage[] Messages { get; set; }
}

Secondly, your JSON contains polymorphic type hints in the DataContractJsonSerializer format. The JSON serializer you are using, Json.NET, does not support this format. Therefore, you might consider switching to DataContractJsonSerializer. Using it I was able to deserialize your JSON as follows:

public enum WebClientMessageType
{
    KeepAliveMessage,
}

[KnownType(typeof(KeepAliveMessage))]
[DataContract(Name="WebClientMessage", Namespace="http://schemas.datacontract.org/2004/07/Data.WebGateway")]
public abstract class WebClientMessage
{
    public WebClientMessage() { }

    [DataMember]
    public int MessageId { get; set; }

    [DataMember]
    public WebClientMessageType Type { get; set; }
}

[DataContract(Name = "KeepAliveMessage", Namespace = "http://schemas.datacontract.org/2004/07/Data.WebGateway")]
public class KeepAliveMessage : WebClientMessage
{
    public KeepAliveMessage() { }

    [DataMember]
    public int PositionInQueue { get; set; }
}

public static class DataContractJsonSerializerHelper
{
    public static string GetJson<T>(T obj, DataContractJsonSerializer serializer)
    {
        using (var memory = new MemoryStream())
        {
            serializer.WriteObject(memory, obj);
            memory.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(memory))
            {
                return reader.ReadToEnd();
            }
        }
    }

    public static string GetJson<T>(T obj)
    {
        var serializer = new DataContractJsonSerializer(typeof(T));
        return GetJson(obj, serializer);
    }

    public static T GetObject<T>(string json, DataContractJsonSerializer serializer)
    {
        using (var stream = GenerateStreamFromString(json))
        {
            var obj = serializer.ReadObject(stream);
            return (T)obj;
        }
    }

    public static T GetObject<T>(string json)
    {
        var serializer = new DataContractJsonSerializer(typeof(T));
        return GetObject<T>(json, serializer);
    }

    private static MemoryStream GenerateStreamFromString(string value)
    {
        return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
    }
}

And then, to test:

    public static void Test()
    {
        // Note there cannot be a space between the "{" and the "_type": 
        string json = @"{
            ""WaitForClientMessagesResult"": [
                {""__type"": ""KeepAliveMessage:#Data.WebGateway"",
                    ""MessageId"": 1,
                    ""Type"": 0,
                    ""PositionInQueue"": -1
                }
            ]
        }";
        var result = DataContractJsonSerializerHelper.GetObject<WaitForClientMessagesResult>(json);
        var newJson = DataContractJsonSerializerHelper.GetJson(result);

        Debug.Assert(JToken.DeepEquals(JToken.Parse(json), JToken.Parse(newJson))); // No assert
    }

If you want to stick with Json.NET you will need to write your own JsonConverter that parses the "__type" property and deserializes the correct concrete type.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

Here's how I ended up resolving this. Its a bit of hack, but that will have to do for now:

    Dictionary<string, object> deserialized = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
    JArray messagesArray = deserialized["WaitForClientMessagesResult"] as JArray;

    foreach (JObject gatewayMessage in messagesArray.Children<JObject>())
    {
        string messageJson = gatewayMessage.ToString();
        WebClientMessageType messageType = GetMessageType(gatewayMessage);
        WaitForClientMessagesResult msg = null;

        switch (messageType)
        {
            case WebClientMessageType.KeepAlive:
                msg = JsonConvert.DeserializeObject<KeepAliveMessage>(messageJson);
                break;
            default:
                break;
        }

    }

I've stripped out other business logic, and just posted the Json handling. If there a better way, I will explore that later.

Thnx for the help everyone :) I couldn't got here without your feedback

Matt

tatmanblue
  • 1,176
  • 2
  • 14
  • 30