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.