Search code examples
c#jsonapipublish-subscribetwitch

Deserialize a complex JSON in C#, Twitch Pubsub


I get this Json string from the Channel Points Event from the Twitch Pubsub System.

{
"type": "reward-redeemed",
"data": {
  "timestamp": "2019-11-12T01:29:34.98329743Z",
  "redemption": {
    "id": "9203c6f0-51b6-4d1d-a9ae-8eafdb0d6d47",
    "user": {
      "id": "30515034",
      "login": "davethecust",
      "display_name": "davethecust"
    },
    "channel_id": "30515034",
    "redeemed_at": "2019-12-11T18:52:53.128421623Z",
    "reward": {
      "id": "6ef17bb2-e5ae-432e-8b3f-5ac4dd774668",
      "channel_id": "30515034",
      "title": "hit a gleesh walk on stream",
      "prompt": "cleanside's finest \n",
      "cost": 10,
      "is_user_input_required": true,
      "is_sub_only": false,
      "image": {
        "url_1x": "https://static-cdn.jtvnw.net/custom-reward-images/30515034/6ef17bb2-e5ae-432e-8b3f-5ac4dd774668/7bcd9ca8-da17-42c9-800a-2f08832e5d4b/custom-1.png",
        "url_2x": "https://static-cdn.jtvnw.net/custom-reward-images/30515034/6ef17bb2-e5ae-432e-8b3f-5ac4dd774668/7bcd9ca8-da17-42c9-800a-2f08832e5d4b/custom-2.png",
        "url_4x": "https://static-cdn.jtvnw.net/custom-reward-images/30515034/6ef17bb2-e5ae-432e-8b3f-5ac4dd774668/7bcd9ca8-da17-42c9-800a-2f08832e5d4b/custom-4.png"
      },
      "default_image": {
        "url_1x": "https://static-cdn.jtvnw.net/custom-reward-images/default-1.png",
        "url_2x": "https://static-cdn.jtvnw.net/custom-reward-images/default-2.png",
        "url_4x": "https://static-cdn.jtvnw.net/custom-reward-images/default-4.png"
      },
      "background_color": "#00C7AC",
      "is_enabled": true,
      "is_paused": false,
      "is_in_stock": true,
      "max_per_stream": { "is_enabled": false, "max_per_stream": 0 },
      "should_redemptions_skip_request_queue": true
    },
    "user_input": "yeooo",
    "status": "FULFILLED"
    }
  }
}

And I want to convert this string into following variables: Edit: I updated these Clases with Json2CSharp.com

public class User
    {
        public string id { get; set; }
        public string login { get; set; }
        public string display_name { get; set; }
    }

    public class Image
    {
        public string url_1x { get; set; }
        public string url_2x { get; set; }
        public string url_4x { get; set; }
    }

    public class DefaultImage
    {
        public string url_1x { get; set; }
        public string url_2x { get; set; }
        public string url_4x { get; set; }
    }

    public class MaxPerStream
    {
        public bool is_enabled { get; set; }
        public int max_per_stream { get; set; }
    }

    public class Reward
    {
        public string id { get; set; }
        public string channel_id { get; set; }
        public string title { get; set; }
        public string prompt { get; set; }
        public int cost { get; set; }
        public bool is_user_input_required { get; set; }
        public bool is_sub_only { get; set; }
        public Image image { get; set; }
        public DefaultImage default_image { get; set; }
        public string background_color { get; set; }
        public bool is_enabled { get; set; }
        public bool is_paused { get; set; }
        public bool is_in_stock { get; set; }
        public MaxPerStream max_per_stream { get; set; }
        public bool should_redemptions_skip_request_queue { get; set; }
    }

    public class Redemption
    {
        public string id { get; set; }
        public User user { get; set; }
        public string channel_id { get; set; }
        public string redeemed_at { get; set; }
        public Reward reward { get; set; }
        public string user_input { get; set; }
        public string status { get; set; }
    }

    public class Data
    {
        public string timestamp { get; set; }
        public Redemption redemption { get; set; }
    }

    public class Root
    {
        public string type { get; set; }
        public Data data { get; set; }
    }

I tried different methodes to deseralize a Json string but nothing realy worked. My last attempt was following: (edited)

private static void SocketMessage(object sender, MessageEventArgs e)
        {
            try
            {
                Console.WriteLine(e.Data);
                //string json = e.Data.Replace("\"{", "{").Replace("\\", string.Empty);
                string json = e.Data;
                Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(json);
                Console.WriteLine(myDeserializedClass.data.redemption.reward.title);
            }
            catch(Exception ex)
            {
                Console.Write(ex);
            }
        }

.NET Framework 4.7.1

I would be very thankful if someone could help me :)

Test String:

{"type":"MESSAGE","data":{"topic":"channel-points-channel-v1.196174120","message":"{\"type\":\"reward-redeemed\",\"data\":{\"timestamp\":\"2021-01-04T13:36:47.746629895Z\",\"redemption\":{\"id\":\"c664b1d8-65a6-4fb9-bef0-7b90a5a3819d\",\"user\":{\"id\":\"196174120\",\"login\":\"p90ez\",\"display_name\":\"P90Ez\"},\"channel_id\":\"196174120\",\"redeemed_at\":\"2021-01-04T13:36:47.746629895Z\",\"reward\":{\"id\":\"0452f6cb-cb1c-4c8e-9978-7103d01b621a\",\"channel_id\":\"196174120\",\"title\":\"Willkommenssound\",\"prompt\":\"Du erhälst deinen eigenen Command mit einem Sound deiner Wahl (bitte mir den Link auf Discord etc. schicken)! Für non-Subs max 15 Sekunden, für Subs bis zu 30 Sekunden.\\n(geklaut von PrideGaymer)\",\"cost\":15000,\"is_user_input_required\":false,\"is_sub_only\":false,\"image\":{\"url_1x\":\"https://static-cdn.jtvnw.net/custom-reward-images/196174120/0452f6cb-cb1c-4c8e-9978-7103d01b621a/3feba875-7151-45e8-8bf1-09d78e48baf1/custom-1.png\",\"url_2x\":\"https://static-cdn.jtvnw.net/custom-reward-images/196174120/0452f6cb-cb1c-4c8e-9978-7103d01b621a/3feba875-7151-45e8-8bf1-09d78e48baf1/custom-2.png\",\"url_4x\":\"https://static-cdn.jtvnw.net/custom-reward-images/196174120/0452f6cb-cb1c-4c8e-9978-7103d01b621a/3feba875-7151-45e8-8bf1-09d78e48baf1/custom-4.png\"},\"default_image\":{\"url_1x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-1.png\",\"url_2x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-2.png\",\"url_4x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-4.png\"},\"background_color\":\"#FF9138\",\"is_enabled\":true,\"is_paused\":false,\"is_in_stock\":true,\"max_per_stream\":{\"is_enabled\":false,\"max_per_stream\":0},\"should_redemptions_skip_request_queue\":true,\"template_id\":null,\"updated_for_indicator_at\":\"2020-02-04T23:20:28.600840418Z\",\"max_per_user_per_stream\":{\"is_enabled\":false,\"max_per_user_per_stream\":0},\"global_cooldown\":{\"is_enabled\":false,\"global_cooldown_seconds\":0},\"redemptions_redeemed_current_stream\":null,\"cooldown_expires_at\":null},\"status\":\"FULFILLED\"}}}"}}

Solution

  • There are many things unclear in the question to start with. The issue was not clear, the input data and the expectations were not explained properly.

    At the later stage it is revealed that the JSON shared in the question is not value coming out of e.Data.

    Anyways, moving to solving the real problem.

    e.Data itself is a JSON string and it has one child property which in-turn is a JSON string.

    Hence, two fold JSON desrialization is required.

    Following classes you need for the first level of deserialization.

    public class MessageData
    {
        public string topic { get; set; }
        public string message { get; set; }
    }
    
    public class DataRoot
    {
        public string type { get; set; }
        public MessageData data { get; set; }
    }
    

    the string of e.Data represents the JSON structure of DataRoot class. So you need to first deserialize DataRoot object as following.

    var rootData = JsonConvert.DeserializeObject<DataRoot>(e.Data);
    

    Now the next JSON (which is shared first in the question) is available inside rootData.data.message property. To deserialize it, you need following classes.

    public class User
    {
        public string id { get; set; }
        public string login { get; set; }
        public string display_name { get; set; }
    }
    
    public class Image
    {
        public string url_1x { get; set; }
        public string url_2x { get; set; }
        public string url_4x { get; set; }
    }
    
    public class DefaultImage
    {
        public string url_1x { get; set; }
        public string url_2x { get; set; }
        public string url_4x { get; set; }
    }
    
    public class MaxPerStream
    {
        public bool is_enabled { get; set; }
        public int max_per_stream { get; set; }
    }
    
    public class Reward
    {
        public string id { get; set; }
        public string channel_id { get; set; }
        public string title { get; set; }
        public string prompt { get; set; }
        public int cost { get; set; }
        public bool is_user_input_required { get; set; }
        public bool is_sub_only { get; set; }
        public Image image { get; set; }
        public DefaultImage default_image { get; set; }
        public string background_color { get; set; }
        public bool is_enabled { get; set; }
        public bool is_paused { get; set; }
        public bool is_in_stock { get; set; }
        public MaxPerStream max_per_stream { get; set; }
        public bool should_redemptions_skip_request_queue { get; set; }
    }
    
    public class Redemption
    {
        public string id { get; set; }
        public User user { get; set; }
        public string channel_id { get; set; }
        public string redeemed_at { get; set; }
        public Reward reward { get; set; }
        public string user_input { get; set; }
        public string status { get; set; }
    }
    
    public class Data
    {
        public string timestamp { get; set; }
        public Redemption redemption { get; set; }
    }
    
    public class Root
    {
        public string type { get; set; }
        public Data data { get; set; }
    }
    

    rootData.data.message represents the JSON structure of Root class declared above. So you need to deserialize it as following.

    var json = rootData.data.message;
    var myDeserializedClass = JsonConvert.DeserializeObject<Root>(json);
    

    So the final version of the code will look like following.

    private static void SocketMessage(object sender, MessageEventArgs e)
    {
        try
        {
            var rootData = JsonConvert.DeserializeObject<DataRoot>(e.Data);
    
            var json = rootData.data.message;
    
            Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(json);
            Console.WriteLine(myDeserializedClass.data.redemption.reward.title);
        }
        catch(Exception ex)
        {
            Console.Write(ex);
        }
    }
    

    I hope this will help you resolve your issue.