skip to Main Content

I am working on a C# app that fetches data from an API. The response from the API has blocks that contain a node Data that changes in structure and only shares one attribute, Type. I am using Newtonsoft. How can I deserialize the Data node to the exact class based on the Type attribute? So that if I create an items array, when I query the array I can switch on the Type attribute and know which object maps to it.

The JSON response from the API looks like this:

{
    "nextLink": "NextLink",
    "value": [
        {
            "Block": {
                "BlockName": "Name",
                "BlockClass": "Class",
                "IsVisibile": true,
                "Data": {
                    "Type": "Footer",
                    "Values": [
                        { 
                            "Property1": "Property1",
                            "Property2": "Property2"
                        }
                    ],
                    "Show": false
                }
            }
        },
        {
            "Block": {
                "BlockName": "Name",
                "BlockClass": "Class",
                "IsVisibile": true,
                "Data": {
                    "Type": "Header",
                    "Title": "Main title",
                    "Subtitle": "Subtitle"
                }
            }
        },
        {
            "Block": {
                "BlockName": "Name",
                "BlockClass": "Class",
                "IsVisibile": true,
                "Data": {
                    "Type": "Body",
                    "Information": "Info",
                    "AdditionalText": "More text",
                    "Projects": [
                        "Project1",
                        "Project2"
                    ]
                }
            }
        }
    ]
}

These are the classes I have defined based on the API response:

public class Blocks
{
    public string NextLink { get; set; }
    public Response[] Value { get; set; }
}

public class Response
{
    public Block Block { get; set; }
}

public class Block
{
    public string BlockName { get; set; }
    public string BlockClass { get; set; }
    public bool IsVisible { get; set; }
    public Data Data { get; set; }
}

public class Data 
{
    public string Type { get; set; }
    ???
}

public class FooterType
{
    public bool Show { get; set; }
    public object[] Values { get; set; }
}

public class HeaderType 
{
    public string Title { get; set; }
    public string Subtitle { get; set; }
}

public class BodyType
{
    public string Information { get; set; }
    public string AdditionalText { get; set; }
    public object[] Projects { get; set; }
}

If I deserialize the response to the specific Type (like below) it of course works for that node in the JSON but not all of them.

public class Data 
{
    public string Type { get; set; }
    public string Title { get; set; }
    public string Subtitle { get; set; }
}

How can I deserialize each item in the JSON to the specific type it maps to?

3

Answers


  1. Chosen as BEST ANSWER

    Combined @Martin and @Serge's solution with this. Thanks for the help here.

    [JsonConstructor]
    public Block (JObject data)
    {
        if (data.TryGetValue("Type", out var type))
        {
            this.Data = (Data)data.ToObject(Type.GetType($"namespace.qualified.{type}"));
        }
    
        //JsonSerializer serializer = new JsonSerializer();
    
        //if (data.TryGetValue("Type", out var type))
        //{
        //    switch (type.ToString())
        //    {
        //        case "HeaderType":
        //            this.Data = new HeaderType();
        //            break;
        //        case "FooterType":
        //            this.Data = new FooterType();
        //            break;
        //        case "BodyType":
        //            this.Data = new BodyType();
        //            break;
        //    }
    
        //    serializer.Populate(data.CreateReader(), this.Data);
        //}
    
        //throw new NotImplementedException($"Cannot deserialize {data}");
    }
    

  2. You can accomplish this by writing your own converter which is doing the class selection

    This example is using Newtonsoft as De/serialization libary. Json.Net

    public class DataConverter : JsonConverter
        {
            public override bool CanConvert(Type objectType) => typeof(Data).IsAssignableFrom(objectType); 
    
            public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
            {
                var obj = JObject.Load(reader);
                // Read the target Type from the json data object
                var dataType = (string)obj["Type"];
                var item = new Data();
                // Determine what is the correct sub class for your data
                switch(dataType)
                {
                    case "Header": 
                        item = new HeaderType();
                        break;
                    case "Body": 
                        item = new BodyType();
                        break;
                    case "Footer": 
                        item = new FooterType();
                        break;
                }
                // Populate the instance with the data provided in json
                serializer.Populate(obj.CreateReader(), item);
                return item;
            }
    
            public override bool CanWrite => false;
            public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotSupportedException();
        }
    

    Here is the working example for your case in dotnetfiddle

    // Then just use it in your ´JsonConvert.DeserializeObject´ method
    var blocks = JsonConvert.DeserializeObject<Blocks>(json, new DataConverter());
    
    Login or Signup to reply.
  3. The simpliest way is to create the polymorphic clases and use a json property constructor

        Root root = JsonConvert.DeserializeObject<Root>(json);
    
    public class Root
    {
        public string nextLink { get; set; }
        public List<Item> value { get; set; }
    }
    public class Item
    {
        public Block Block { get; set; }
    }
    public class Block
    {
        public string BlockName { get; set; }
        public string BlockClass { get; set; }
        public bool IsVisibile { get; set; }
        public DataBase Data { get; set; }
        
        [JsonConstructor]
        public Block (JObject Data)
        {
            this.Data = (DataBase)Data.ToObject(Type.GetType((string)Data["Type"]));
        }
        public Block() {}
    }
    
    public class DataBase
    {
        public string Type { get; set;}
        //public string Type { get { return this.GetType().Name; } }
    }
    
    public class Footer : DataBase
    {
        public List<Properties> Values { get; set; }
        public bool Show { get; set; }
    }
    
    public class Header : DataBase
    {
        public string Title { get; set; }
        public string Subtitle { get; set; }
    }
    
    public class Body : DataBase
    {
        public string Information { get; set; }
        public string AdditionalText { get; set; }
        public List<string> Projects { get; set; }
    }
    
    public class Properties
    {
        public string Property1 { get; set; }
        public string Property2 { get; set; }
    }
    

    but if you have a lot of json of this kind, instead of adding one line to a constructor you can create a json property converter

    Root root = JsonConvert.DeserializeObject<Root>(json);
    
    public class DataPropertyConverter : JsonConverter<DataBase>
    {
        public override DataBase ReadJson(JsonReader reader, Type objectType, DataBase existingValue, bool hasExistingValue, JsonSerializer serializer)
        {
            var jObj = JObject.Load(reader);
             return (DataBase)jObj.ToObject(Type.GetType((string)jObj["Type"]));
        }
        
        public override bool CanWrite => false;
        public override void WriteJson(JsonWriter writer, DataBase value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
    public class Block
    {
        public string BlockName { get; set; }
        public string BlockClass { get; set; }
        public bool IsVisibile { get; set; }
    
        [JsonConverter(typeof(DataPropertyConverter))]
        public DataBase Data { get; set; }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search