0

I have a data model where several data classes are defined. This data classes are serialized in a Unity project (namely A) and then sent via TCP to another Unity project (B) which acts as a debugging tool. By the way, A and B only share the data model. To deserialize the json on the tool project, I loop through the list of valid deserializable types (i.e. the classes in the data model) until a valid one is found and reconstructed. But after that, I'm doing an if-else asking which type was actually reconstructed in order to send its data to the appropriate UI controller. This last part is fragile imo, since any change in the data model (add, remove class) implies changing code responsible for deserializing the json, so I want to ask if there is a better way to achieve this. Below is an example test code:

// This is behaviour is running on B, the tool project
void Update()
    {
        if (gameData == null)
            return;

        if (Server.GetRecivedAmount() <= 0)
            return;

        var msg = Server.GetRecived().Item1;

        System.Type messageType = null;
        object deserializedMessage = null;
        // Find which type is represented by the msg string
        foreach (System.Type t in deserializableTypes)
        {
            settings.SerializationBinder = new GeneralBinder(t);
            try
            {
                deserializedMessage = JsonConvert.DeserializeObject(msg, settings);
                Debug.Log($"Successful deserialization: {deserializedMessage.GetType()}");
                messageType = t;
                break;
            }
            catch (System.Exception e)
            {
                Debug.Log("Incorrect type: " + e);
            }
        }

        if (messageType == null)
        {
            Debug.LogWarning($"{name} didn't find any valid message type");
            return;
        }
        if (messageType == typeof(AgentWrapper))
        {
            var g = deserializedMessage as AgentWrapper;
            if(g.type == AgentWrapper.Type.NEW) { }
        }
        // Here, there will be more and more if checks and I want to avoid that if possible
    }
  • You could have your classes inherit a base class with a discriminator property. this way you can deserialize to base class first (that contains the class name string), then deserialize again based on the value in descriminator property. I had to do this to get polymorphic classes working with Rust Lang. – Snazzie Aug 17 '23 at 14:07
  • I would use some kind of higher level protocol or message queue, like MQTT, and use different channels or addresses to differentiate different types of messages. Otherwise you need to create your own protocol that does more or less the same thing. Such a protocol can be as simple as using the $type field to do polymorphic de serialization to a common base class. But most protocols seem to use serialization independent strategies. – JonasH Aug 17 '23 at 14:10
  • I would also be skeptical of any custom code using raw TCP. I know it is [difficult to get right](https://blog.stephencleary.com/2009/04/tcpip-net-sockets-faq.html), I would not trust myself to get it right, and I would expect a popular library to be better designed and tested than anything I could write myself. – JonasH Aug 17 '23 at 14:37
  • @JonasH I also looked into the different channels approach. I don't remember much of networking but at first, I thought that you need to open several ports (one for each type of message) and at the moment, I don't know if that is going to be possible when testing with other people/internet setups. But idk, maybe I'm wrong. I will study about MQTT since I agree with using standard protocols. Thanks :) – dnorambu 98 Aug 17 '23 at 15:02
  • 1
    No, you do not need individual ports. The protocol can handle things like that internally. Just consider http, different addresses on the same site will be routed to different methods on the server, while running on port 80/443. – JonasH Aug 17 '23 at 15:08

1 Answers1

1

If all your class models inherits from the the same base class, let's say BaseClass, coupled with JsonKnownType attribute, you could use polymorphism to ensure deserialization and use it this way :

[JsonConverter(typeof(JsonKnownTypesConverter<BaseClass>))]
[JsonKnownType(typeof(AgentWrapper), "agent")]
[JsonKnownType(...)]
[...]
public class BaseClass
{

}

public class AgentWrapper : BaseClass
{
}

Then, you can get you deserialized object using this

JsonConvert.DeserializeObject<BaseClass>(msg);

As @GuruStron pointed out, this package is available on github here. You can find it also with nuget manager.

Mad hatter
  • 569
  • 2
  • 11
  • `JsonKnownTypeAttribute` is not a part of Newtonsoft Json.NET AFAIK. I assume this uses extra package on top of it? – Guru Stron Aug 17 '23 at 14:09
  • @GuruStron Indeed, it's available here https://github.com/dmitry-bym/JsonKnownTypes – Mad hatter Aug 17 '23 at 14:12
  • I have seen that one, yes. Please add to the answer for completeness. – Guru Stron Aug 17 '23 at 14:13
  • @Madhatter Cool, I didn't know about this package, although my classes don't inherit from a common parent rn. Maybe wrapping them in a very generic "SendableData" class (or something like that) can make the difference. – dnorambu 98 Aug 17 '23 at 14:46
  • @dnorambu98 You can also code your own json converter to achieve the same goal, if you don't want to use a third party library. But it will require a hundred lines of code. You can switch also to `System.Text.Json` and use the built-in `JsonDerivedType` attribute – Mad hatter Aug 17 '23 at 14:51