Determine type during json deserialize
Solution 1
It would be helpful if the message could specify its type. Otherwise you have to infer it from some property or another.
You could use a message wrapper class when serializing, like this:
public class MessageWrapper<T>
{
public string MessageType { get { return typeof(T).FullName; } }
public T Message { get; set; }
}
So if you have a class Name
with a First
and Last
property, you could serialize it like this:
var nameMessage = new MessageWrapper<Name>();
nameMessage.Message = new Name {First="Bob", Last = "Smith"};
var serialized = JsonConvert.SerializeObject(nameMessage);
The serialized JSON is
{"MessageType":"YourAssembly.Name","Message":{"First":"Bob","Last":"Smith"}}
When deserializing, first deserialize the JSON as this type:
public class MessageWrapper
{
public string MessageType { get; set; }
public object Message { get; set; }
}
var deserialized = JsonConvert.DeserializeObject<MessageWrapper>(serialized);
Extract the message type from the MessageType
property.
var messageType = Type.GetType(deserialized.MessageType);
Now that you know the type, you can deserialize the Message
property.
var message = JsonConvert.DeserializeObject(
Convert.ToString(deserialized.Message), messageType);
message
is an object
, but you can cast it as Name
or whatever class it actually is.
Solution 2
var log = JsonConvert.DeserializeObject<Log>(File.ReadAllText("log.example.json");
public class Log
{
[JsonConverter(typeof(MessageConverter))]
public object[] Messages { get; set; }
}
public class MessageA
{
public string Message;
}
public class MessageB
{
public int value;
}
public class MessageC
{
public string ValueA;
public string ValueB;
}
public class MessageConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
object ReadMessage(JObject jobject)
{
if (jobject.Property("Message") != null)
return jobject.ToObject<MessageA>(serializer);
if (jobject.Property("value") != null)
return jobject.ToObject<MessageB>(serializer);
if (jobject.Property("ValueA") != null)
return jobject.ToObject<MessageC>(serializer);
throw new Exception("Type is not recognized");
}
var jarray = JArray.Load(reader);
return jarray.Select(jitem => ReadMessage((JObject)jitem)).ToArray();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Json example:
{
"Messages":
[
{"Message": "System halted"},
{"value": 42},
{"ValueA": "Bob", "ValueB": "Smith"}
]
}
Solution 3
Hopefully you are familiar with the factory pattern, you could use factory(s) and include a "Type" property as part of the json, let's call it _t
.
You can either parse the json string yourself and find the _t
property's value, deserialise it to a dynamic
and get jsonObj._t
or have a simple class
with only a _t
field solely to deserialise the json into initially.
Then you can pass this string
representing the C# Type to the factory and get a json deserialiser for that Type
.
You can then make all of your outgoing and incoming calls add and process the _t
parameter respectively, so that new types are easy to add in future, by just adding and registering the serialisers/deserialisers you need for that Type
with the factory(s).
tunafish24
Updated on June 25, 2022Comments
-
tunafish24 almost 2 years
I'm working on a protocol in which the receiver will receive json messages of certain specified custom types (currently 5, but could be 10-20). I'm struggling to come up with an optimal/fast solution which will automatically deserialize the json and return the correct type of object.
Example:
public class MessageA { public string Message; } public class MessageB { public int value; } public class MessageC { public string ValueA; public string ValueB; }
Ideally, the method should be like
Object Deserialize(string json);
and it will return one of the three message types OR null - in case there was a parsing error/the json didn't match any of the predefined type.
UPDATE: I have control over sender/receiver as well as the protocol design.
-
Leandro Bardelli almost 5 yearsNice answer, can it be casted from var message to "Name"?, of course, without make it explicit as DeserializeObject<Name>()
-
JohnB over 3 yearsI was trying a similar approach, but tried to cast the
object Message
to a concrete class and that didn't work. The trick as you pointed out is to convert the propertyMessage.ToString()
first then deserialize that JSON string to your concrete class. -
AndrewSilver over 2 yearsDid you read the previous answers? One of them suggests the same, but with good code samples