Я работаю над приложением С#, которое извлекает данные из API. Ответ от API содержит блоки, содержащие узел Data, который меняет структуру и имеет только один общий атрибут, Type. Я использую Ньютонсофт. Как я могу десериализовать узел Data в точный класс на основе атрибута Type? Так что, если я создаю массив элементов, при запросе массива я могу включить атрибут Type и узнать, какой объект ему соответствует.
Ответ JSON от API выглядит так:
{
"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"
]
}
}
}
]
}
Это классы, которые я определил на основе ответа API:
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; }
}
Если я десериализую ответ на конкретный тип (как показано ниже), он, конечно, работает для этого узла в JSON, но не для всех из них.
public class Data
{
public string Type { get; set; }
public string Title { get; set; }
public string Subtitle { get; set; }
}
Как я могу десериализовать каждый элемент в JSON до определенного типа, с которым он сопоставляется?
Спасибо @Flydog57. Я читал о System.Text.Json, но, к сожалению, он доступен только в .Net7, а я использую .Net6. В любом случае я смог заставить его работать с приведенными ниже решениями. Спасибо.





Вы можете сделать это, написав свой собственный конвертер, который выполняет выбор класса
В этом примере используется Newtonsoft в качестве библиотеки де/сериализации. 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();
}
Вот рабочий пример для вашего случая в dotnetfiddle
// Then just use it in your ´JsonConvert.DeserializeObject´ method
var blocks = JsonConvert.DeserializeObject<Blocks>(json, new DataConverter());
Спасибо, Мартин. В итоге я объединил ваше решение и решение Сержа.
Самый простой способ - создать полиморфные классы и использовать конструктор свойств json.
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; }
}
но если у вас много json такого типа, вместо добавления одной строки в конструктор вы можете создать преобразователь свойств json
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; }
}
Спасибо. Удалось заставить его работать таким образом с оговоркой, что API не возвращает полное пространство имен, которое необходимо Type.GetType(). Я взломал способ, чтобы он работал с вашим решением и @Martin на случай, если я обнаружу проблемы с первым.
То, что вы ищете, называется полиморфной сериализацией и десериализацией. Вы можете это сделать (как зависит от сериализатора - и версии). Судя по всему, последний System.Text.Json делает это из коробки. Заставить его работать может быть неприятно