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.

4

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

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)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.