Determine type during json deserialize
Asked Answered
P

5

13

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.

Peppel answered 31/7, 2016 at 1:29 Comment(2)
Are you the sender, reciever, or do you have control over the content of both?Bitterroot
my solution was to send over the class name as a string in the Json file Deseralize the first time as a JObject to determine the type and the second time to create your objectSyllabogram
C
26

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.

Corps answered 31/7, 2016 at 2:32 Comment(2)
Nice answer, can it be casted from var message to "Name"?, of course, without make it explicit as DeserializeObject<Name>()Busterbustle
I 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 property Message.ToString() first then deserialize that JSON string to your concrete class.Enrica
H
4
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"}
  ]
}
Hyrup answered 5/8, 2021 at 22:9 Comment(0)
B
1

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).

Bitterroot answered 31/7, 2016 at 2:29 Comment(0)
P
0

As an option, you can check if the JSON string contains a class-specific Property name. Fast and easy!

Pulmotor answered 3/11, 2021 at 18:16 Comment(1)
Did you read the previous answers? One of them suggests the same, but with good code samplesBore
P
0

I know this is an old question, but I struggled to find an answer that worked for me. This is what I ended up with...

Create a new empty interface

public interface IMessage { }

and inherit from this interface

public class MessageA : IMessage
{
   public string Message;
}

Then create my own serializer

using Newtonsoft.Json;
public class TypedJsonSerializer
{
    private readonly JsonSerializerSettings _settings;

    public TypedJsonSerializer()
    {
        _settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Objects };
    }

    public string Serialize<TType>(TType obj)
    {
        return JsonConvert.SerializeObject(obj, _settings);
    }

    public TType Deserialize<TType>(string serialized)
    {
            return JsonConvert.DeserializeObject<TType>(serialized, _settings);
    }
}

After deserializing, the resulting object can be tested using the 'is' operator and cast appropriately.

var msg = myTypedSerializer.Deserialize<IMessage>(messageJson);

if (msg is MessageA)
{
    var myMsgA = (MessageA)msg;
}
Psalm answered 10/5 at 1:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.