简体   繁体   中英

Json deserialization class structure on client

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 (eg {"name":"value"}) into type 'System.Collections.Generic.IEnumerable`1[ Red5Prototype.Models.WaitForClientMessagesResult]

because the type requires a JSON array (eg [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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM