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?
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; }
}