Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserializing Webhooks #71

Open
TonyValenti opened this issue Jan 8, 2020 · 2 comments
Open

Deserializing Webhooks #71

TonyValenti opened this issue Jan 8, 2020 · 2 comments

Comments

@TonyValenti
Copy link

Hi @mrb0nj -
How do I deserialize a slack event so I can process it? For example, when a message is posted to a slack room, I get the following JSON:

{
  "team_id": "T6Q4W5TQR",
  "api_app_id": "A8DDL8RUY",
  "event": {
    "client_msg_id": "ba299014-e3ba-4817-88f7-01658ee25466",
    "type": "message",
    "text": "test",
    "user": "U6QNA62CC",
    "ts": "1578520850.002100",
    "team": "T6Q4W5TQR",
    "blocks": [
      {
        "type": "rich_text",
        "block_id": "bImul",
        "elements": [
          {
            "type": "rich_text_section",
            "elements": [
              {
                "type": "text",
                "text": "test"
              }
            ]
          }
        ]
      }
    ],
    "channel": "CSEL7QT7S",
    "event_ts": "1578520850.002100",
    "channel_type": "channel"
  },
  "type": "event_callback",
  "event_id": "EvS4FD9M17",
  "event_time": 1578520850,
  "authed_users": [
    "U6QNA62CC"
  ]
}

How do I turn this into the appropriate object?

@mrb0nj
Copy link
Owner

mrb0nj commented Feb 27, 2020

Hi @TonyValenti thanks for your interesting question, I hope you have found the solution to your problem by now (apologies for taking so long) but in case you haven't (and for my future reference) I came up with a solution....

for the most part it's not all that tricky, you add the Newtonsoft JSON.net nuget package and create a parent class with properties that match the payload, similar to the SlackMessage then you can use JsonConvert.Deserialize(jsonString) to create the object.

The interesting part, however, is around the Blocks/Elements that can be contained in a message, in this project, the inherit from a base object Block and set a 'Type' - when trying to deserialize, the dotnet type is lost meaning you wouldn't get back the same objects as you would have done if you'd made the message with SlackMessage. They way you can do it, is with a custom JsonConverter (part of JSON.net) like this:

public class BlockConverter : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Block) 
                || objectType == typeof(Element)
                || objectType == typeof(IContextElement) 
                || objectType == typeof(IActionElement) 
                || objectType == typeof(IInputElement);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);

        var typeValue = jo.Properties().FirstOrDefault(p => p.Name.Equals("type", StringComparison.OrdinalIgnoreCase));
        
        if(typeValue != null)
        {
            if(objectType == typeof(Block))
            {
                var blockType = Enum.Parse(typeof(BlockType), typeValue.Value.ToString(), true);

                switch(blockType)
                {
                    case BlockType.Actions:
                        objectType = typeof(Actions);
                        break;
                    case BlockType.Context:
                        objectType = typeof(Context);
                        break;
                    case BlockType.Divider:
                        objectType = typeof(Divider);
                        break;
                    case BlockType.File:
                        objectType = typeof(File);
                        break;
                    case BlockType.Image:
                        objectType = typeof(Blocks.Image);
                        break;
                    case BlockType.Input:
                        objectType = typeof(Input);
                        break;
                    case BlockType.Section:
                        objectType = typeof(Section);
                        break;
                }
            } 
            else 
            {
                if(typeValue.Value.ToString().Equals("plain_text", StringComparison.OrdinalIgnoreCase)
                    ||typeValue.Value.ToString().Equals("mrkdwn", StringComparison.OrdinalIgnoreCase))
                    {
                        objectType = typeof(TextObject);
                    }
                    else
                    {
                        var elementType = (ElementType)typeValue.Value.ToObject(typeof(ElementType), serializer);

                        switch(elementType)
                        {
                            case ElementType.Button:
                                objectType = typeof(Button);
                                break;
                            case ElementType.DatePicker:
                                objectType = typeof(DatePicker);
                                break;
                            case ElementType.Image:
                                objectType = typeof(Elements.Image);
                                break;
                            case ElementType.MultiSelectChannels:
                                objectType = typeof(MultiSelectChannels);
                                break;
                            case ElementType.MultiSelectConversations:
                                objectType = typeof(MultiSelectConversations);
                                break;
                            case ElementType.MultiSelectExternal:
                                objectType = typeof(MultiSelectExternal);
                                break;
                            case ElementType.MultiSelectStatic:
                                objectType = typeof(MultiSelectStatic);
                                break;
                            case ElementType.MultiSelectUsers:
                                objectType = typeof(MultiSelectUsers);
                                break;
                            case ElementType.Overflow:
                                objectType = typeof(Overflow);
                                break;
                            case ElementType.PlainTextInput:
                                objectType = typeof(PlainTextInput);
                                break;
                            case ElementType.RadioButtons:
                                objectType = typeof(RadioButtons);
                                break;
                            case ElementType.SelectChannels:
                                objectType = typeof(SelectChannels);
                                break;
                            case ElementType.SelectConversations:
                                objectType = typeof(SelectConversations);
                                break;
                            case ElementType.SelectExternal:
                                objectType = typeof(SelectExternal);
                                break;
                            case ElementType.SelectStatic:
                                objectType = typeof(SelectStatic);
                                break;
                            case ElementType.SelectUsers:
                                objectType = typeof(SelectUsers);
                                break;
                            
                        }
                    }
            }
        }

        return jo.ToObject(objectType, serializer);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

this will examine the text representation of the block and tell JSON.net to convert that bit back into the right object. This is how you would use it:

class Program
{
    
    private static readonly DefaultContractResolver _resolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() };
    private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
    {
        ContractResolver = _resolver,
        NullValueHandling = NullValueHandling.Ignore,
        Converters = new List<JsonConverter> { new BlockConverter() }
    };
    static void Main(string[] args)
    {
        var message = new SlackMessage();
        message.Blocks = new List<Block>();
        message.Blocks.Add(new Divider());
        message.Blocks.Add(new Blocks.Image());
        message.Blocks.Add(new Section());
        message.Blocks.Add(new Context() { Elements = new List<Interfaces.IContextElement> { new Elements.Image(), new TextObject() }});

        var json = ApiBase.SerializeObject(message);

        Console.WriteLine($"Start: {json}");
        

        var obj = JsonConvert.DeserializeObject<SlackMessage>(json, _serializerSettings);
        Console.WriteLine("Hello World!");
    }
}

I hope this helps yourself or anyone else interested and this functionality will definitely make it into an upcoming version of this library!

@mrb0nj
Copy link
Owner

mrb0nj commented Feb 27, 2020

Although, looking at your sample, the Block Kit doesn't contain a definition for rich_text or rich_text_section, will investigate and try and implement!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants