0

I am working on a websocket client application. The server send messages in JSON format and I want to deserialize it. There have one string in the JSON format data that shows the type of message (it has about 50 types today, maybe it will have more in the future).

So I have written a large switch statement like this:

switch(type){
    case "type1":
        DoSth<T1>(DeserializeFunction<T1>(message));
        break;
    case "type2":
        DoSth<T2>(DeserializeFunction<T2>(message));
        break;
    //...
}

Is it possible to optimize this statement?

This is the model:

public record EventMessage<T> where T : IEventExtraBody
    {
        // this will always be 0
        [JsonPropertyName("s")]
        public int EventType { get; set; }
        
        [JsonPropertyName("sn")]
        public long SerialNumber { get; set; }
        
        [JsonPropertyName("d")]
        public EventMessageData<T> Data { get; set; }

        public override string ToString()
        {
            return JsonSerializer.Serialize(this);
        }
    }

public record EventMessageData<T> where T : IEventExtraBody
    {
        // Some other properties        

        [JsonPropertyName("extra")]
        public EventMessageExtra<T> Extra { get; set; }
    }

public record EventMessageExtra<T> where T : IEventExtraBody
    {
        [JsonPropertyName("type")]
        public string Type { get; set; } // this string indicates the type of message
        
        [JsonPropertyName("body")]
        public T Body { get; set; }
    }

Body (an example):

public record ExitedGuildEvent : IEventExtraBody
    {
        [JsonPropertyName("user_id")]
        public string UserId { get; set; }

        [JsonPropertyName("exited_at")]
        public long ExitedAt { get; set; }
    }

When message arrived, I use JsonDocument to get the type string.

var typeString = JsonDocument.Parse(message.Text).RootElement.GetProperty("d").GetProperty("extra").GetProperty("type").GetString()

Then, I want to deserialize the message and publish it to MessageHub.

Deserializing the json string and publish:

_messageHub.Publish(JsonSerializer.Deserialize<EventMessage<BodyType>>(message.Text));

And because there are lots of BodyType, and EventMessage<Type.GetType("TypeClassPath")>(message.Text) is illegal, I write a large switch statement.

Maybe I have build a very bad model for this situation. I hope you can give me some advice.

ZygD
  • 22,092
  • 39
  • 79
  • 102
Liam Sho
  • 11
  • 2
  • Need more information as it does not have minimal code to visualize the issue. – dotnetstep May 23 '21 at 03:13
  • The problem with such design is that you won't get a typed instance. Or `dynamic` ? https://stackoverflow.com/questions/10969681/generic-class-how-to-set-the-type-in-runtime and https://stackoverflow.com/questions/43899490/create-instance-of-generic-class-with-dynamic-generic-type-parameter and https://stackoverflow.com/questions/1606966/generic-method-executed-with-a-runtime-type and https://stackoverflow.com/questions/67463200/how-to-shallow-copy-of-a-list-given-as-generic-without-knowing-the-items-generic/67464693#67464693 and https://www.google.com/search?client=firefox-b-d&q=quicktype-vs –  May 23 '21 at 04:11
  • You know SignalR is based on WebSockets and does all this for you like magic. – Andy May 24 '21 at 16:30
  • if you use hierarchical switch statement , you will reach to Log(n) time complexity instead of O(n) – A Farmanbar May 24 '21 at 16:49

2 Answers2

0

You could replace switch-case with a hashmap. To do that you just need to move every case into separate function. Here you can create a factory method to help you to fill out a hashmap because cases are pretty similar

public class YourHub
{
    private IMessageHub _messageHub = new MessageHub();
    private Dictionary<string, Action<string, IMessageHub>> _methods;

    public YourHub()
    {
        //fill out the hashmap for all types that you have
        //make sure this hashmap is shared between operations
        _methods = new Dictionary<string, Action<string, IMessageHub>>()
        {
            {"key1",  CreateAction<EventMessage<ExitedGuildEvent>>() }
        };
    }

    //factory method for the actions
    private Action<string, IMessageHub> CreateAction<T>()
    {
        return (json, hub) => hub.Publish(JsonSerializer.Deserialize<T>(json, null));
    }

    public void ProcessMessage(string json)
    {
        var typeString = JsonDocument
             .Parse(json)
             .RootElement.GetProperty("d")
             .GetProperty("extra")
             .GetProperty("type")
             .GetString();
                    
        if (!_methods.ContainsKey(typeString)) throw new NotSupportedException();            
        var method = _methods[typeString]; 

        method(json, _messageHub);
    }      
}

This aproach won't give you a huge perfomance boost on 50 elements, but it looks cleaner. The runtime complexity is O(1) compared to O(n) with switch-case, but it takes O(n) additional space.

Alexander Mokin
  • 2,264
  • 1
  • 9
  • 14
0

A better solution than a big switch would probably be to refactor DeserializeFunction into an interface and class.

Register It by type and then resolve it. Either with a DI container or by a dictionary where you map.

interface IMessageDeserializer {

    object Deserialize(Message message);

}

class Type1Deserializer : IMessageDeserializer {

    public object Deserialize(Message message){
      // Implementation that returns a Type1
      return new Type1(){

      };
    }

}


// Register your serializers (you can use also a DI container  but this is simpler just to show how) in a dictionary, preferably reused

Dictionary<Type, IMessageDeserializer> serializers = new Dictionary<Type, IMessageDeserializer>();

serializers.Add("type1", new Type1Deserializer());
serializers.Add("type2", new Type2Deserializer());
serializers.Add("type3", new Type3Deserializer());

// When you need it, use it like this:

string type = "type1"; // This is from your other code
var message = GetMessage(); // This is from your other code

IMessageDeserializer serializer = serializers[type];    
object deserializedMessage = serializer.Deserialize(message);

// To create your event message, either add a constraint to the T of IMessageDeserializer so you can pass it into another function that creates the event message or just simply return the messagehub message as json directly from your IMessageDeserializer implementation)

(I wrote this from memory so I apologise for any mistakes)

Jonas Stensved
  • 14,378
  • 5
  • 51
  • 80