jsonjson-deserializationazure-web-pubsub

Azure PubSub / JSON subprotocol / deserialize JSON payload


So JSON is going to be around for awhile, and I'm going to have to learn how it works things out....

In Azure pubsub, I send a message like this:

 var json = System.Text.Json.JsonSerializer.Serialize(<<a class in C#>> );
 await webPubSubServiceClient.SendToAllAsync(content: json, contentType: 
  ContentType.ApplicationJson);

When I get it on the other side (as its read from ResponseMessage.TEXT), I get this:

{
  "type": "message",
  "from": "server",
  "dataType": "json", //binary/json/text
  "data": {
    "event": "connected",
    "userId": "WPFClient_162035",
    "connectionId": "",
    "message": "User: >WPFClient_162035< has joined hub:>chat<",
    "type": "system",
    "group": "",
    "typeName": "WebhostGeneratedMessage"
  }
}

The webPubSubServiceClient has wrapped the JSON I created within this structure:

 {
      "type": "message",
      "from": "server",
      "dataType": "json", //binary/json/text
      "data": {}

The problem with this is that I'm sending various JSON payloads, and the wrapper does not have an envelope field TELLing me how to deserialize the contents of what is in "data" - as within that class I have a base class with a property that fills in the typeName that I'm using.

So when I do this:

TypeOfMessageReceived? deserialized = System.Text.Json.JsonSerializer.Deserialize<TypeOfMessageReceived>(body);

The deserialize has the change of blowing up on the data element, as the data element can be any class that I've serialized.

So how can I read data with a class in C#? Do I read it as a JSONElement/JSONDocument and parse my way to it? If so, I would have hoped there were some examples of best practices.

I think my other option is to simply define the message being sent as "TEXT", and then deserialize to a base class, grab the segment I need, deserialize that fragment?

I'm missing something here, as the JSON protocol should help me here, and not get in my way... or am I just thinking too strongly typed?

I'm using NET6, so I'm hoping we have some new features I'm not aware of. As I said, I'm looking for best practises using this new service.

Thanks in advance.


Solution

  • How about checking the typeName first and then deserializing to the strong type:

    using (var document = JsonDocument.Parse(inputMessage))
    {
        var type = document.RootElement.GetProperty("type").GetString();
        if (type == "message")
        {
            var data = document.RootElement.GetProperty("data");
            if (data.ValueKind == JsonValueKind.Object && data.TryGetProperty("typeName", out var typeNameProperty))
            {
                var typeName = typeNameProperty.GetString();
                if (typeName == "WebhostGeneratedMessage")
                {
                    var message = data.Deserialize<WebhostGeneratedMessage>(new JsonSerializerOptions(JsonSerializerDefaults.Web));
    
                    Console.WriteLine(message?.Message ?? throw new ArgumentNullException());
                }
            }
        }
    }
    

    A full runnable net6 console test app:

    
    using System.Text.Json;
    
    var inputMessage = JsonSerializer.Serialize(
        new
        {
            type = "message",
            from = "server",
            dataType = "json",
            data = new
            {
                @event = "connected",
                userId = "WPFCLient_142322",
                connectionId = "12345",
                message = "User a joined",
                type = "system",
                group = "group1",
                typeName = "WebhostGeneratedMessage"
    
            }
        });
    
    using (var document = JsonDocument.Parse(inputMessage))
    {
        var type = document.RootElement.GetProperty("type").GetString();
        if (type == "message")
        {
            var data = document.RootElement.GetProperty("data");
            if (data.ValueKind == JsonValueKind.Object && data.TryGetProperty("typeName", out var typeNameProperty))
            {
                var typeName = typeNameProperty.GetString();
                if (typeName == "WebhostGeneratedMessage")
                {
                    var message = data.Deserialize<WebhostGeneratedMessage>(new JsonSerializerOptions(JsonSerializerDefaults.Web));
    
                    Console.WriteLine(message?.Message ?? throw new ArgumentNullException());
                }
            }
        }
    }
    
    public class WebhostGeneratedMessage
    {
        public string? Event { get; set; }
        public string? UserId { get; set; }
        public string? ConnectionId { get; set; }
        public string? Message { get; set; }
        public string? Type { get; set; }
        public string? Group { get; set; }
        public string? TypeName { get; set; } = nameof(WebhostGeneratedMessage);
    }