Динамически десериализовать JSON в производные типы на основе контента?

В моей маленькой библиотеке, которую я пишу как побочный проект, я использую RestSharp для получения Json из веб-API. Десериализация в классы моделей отлично работает для простых типов, но есть конечные точки, где результирующий тип неизвестен (или неясен) во время запроса.

В частности, это API GuildWars2 v1, и одним из примеров могут быть данные предметов. Конечно, у каждого элемента есть основные свойства, а дополнительные значения устанавливаются в зависимости от запрашиваемого элемента. Например, у оружия есть какие-то модификаторы и так далее.

Моя идея состояла в том, чтобы создать абстрактный класс Item для всех основных свойств и получить от этого класса все существующие подтипы элементов, добавляя необходимые свойства. Моя проблема в том, как решить, в какой подкласс десериализовать. Я уже читал о различных методах, связанных с добавлением полного имени типа в строку Json, но, поскольку я получаю данные только из API, я не могу повлиять на получаемый им ответ.

В идеале я всегда хотел бы десериализоваться в элемент базового класса, а десериализатор определяет фактический тип с помощью заданных свойств, содержащихся в строке Json. Возможно ли это? Или есть лучший способ решить эту проблему?

заранее спасибо

РЕДАКТИРОВАТЬ: Это пример Json оружия:

{
  "item_id": "30704",
  "name": "Twilight",
  "description": "",
  "type": "Weapon",
  "level": "80",
  "rarity": "Legendary",
  "vendor_value": "100000",
  "icon_file_id": "456031",
  "icon_file_signature": "CE3AF0B7B9BB6244726779F5B6A930541BA6C15F",
  "game_types": ["Activity", "Dungeon", "Pve", "Wvw"],
  "flags": ["HideSuffix", "NoSell", "SoulBindOnUse"],
  "restrictions": [],
  "weapon": {
    "type": "Greatsword",
    "damage_type": "Physical",
    "min_power": "995",
    "max_power": "1100",
    "defense": "0",
    "infusion_slots": [],
    "infix_upgrade": {
      "attributes": [
        {
          "attribute": "Power",
          "modifier": "179"
        },
        {
          "attribute": "Power",
          "modifier": "179"
        }
      ]
     },
     "suffix_item_id": "24599"
   }
}

А это будет часть брони:

{
  "item_id":"500",
  "name":"Carrion Conjurer Chest of Dwayna", 
  "description":"",
  "type":"Armor",
  "level":"68",
  "rarity":"Rare",
  "vendor_value":"257",
  "icon_file_id":"61023",
  "icon_file_signature":"76CD08463A05730071D400254141B50E570662D3",
  "game_types":["Activity", "Dungeon", "Pve", "Wvw"],
  "flags":["SoulBindOnUse"],
  "restrictions":[],
  "armor": {
    "type":"Coat",
    "weight_class":"Light",
    "defense":"221",
    "infusion_slots":[],
    "infix_upgrade": {
      "attributes": [
        {
          "attribute":"ConditionDamage","modifier":"70"
        },
        {
          "attribute":"Power","modifier":"50"
        }
      ]
    },
    "suffix_item_id":"24767"
  }
}

Я хотел бы десериализовать на основе содержимого свойства «Тип», но я не могу понять, как это будет работать =/


person MrFloya    schedule 16.08.2013    source источник
comment
Можете ли вы показать пример JSON, который вы получаете от API?   -  person mallan1121    schedule 17.08.2013
comment
@mallan1121 отредактировал мой пост   -  person MrFloya    schedule 17.08.2013


Ответы (1)


Вы можете анализировать данные JSON с помощью JObject, которые затем можно использовать в качестве ассоциативного массива.

string json = @"{
  "item_id": "30704",
  "name": "Twilight",
  "description": "",
  "type": "Weapon",
  ...
}";

JObject o = JObject.Parse(json);

Item item = null;
switch (o["type"])
{
    case "Weapon":
      item = JsonConvert.DeserializeObject<Weapon>(json);
    break;
    case "Armor":
      item = JsonConvert.DeserializeObject<Armor>(json);
    break;
    default:
      // throw error?
}

и тогда у вас будет базовый класс как таковой:

public class Item
{
  public string item_id { get; set; }
  public string name { get; set; }
  public string description { get; set; }
  public string type { get; set; }
  ...
  // List all common properties here
  public Item() { } 
  // It's important to have the public default constructor implemented 
  // if you intend on overloading it for your own purpose later
}

И ваш класс оружия может быть таким:

public class Weapon : Item // Weapon extends Item
{
  public WeaponDetails weapon { get; set; }
  public Weapon() { }
}

И детали оружия:

public class WeaponDetails
{
  public string type { get; set; }
  public string damage_type { get; set; }
  public int min_power { get; set; }
  public int max_power { get; set; }
  ...
  public WeaponDetails() { }
}
person Arthur Cavallari    schedule 28.08.2013
comment
Большое спасибо за ответ! Это именно то, что я себе представлял. У меня только один дополнительный вопрос. Разве этот код кода не будет дважды анализировать JSON? Сначала как JObject, а затем как соответствующий тип? Это не большая проблема, но в случае, если мне нужно разобрать огромное количество JSON, это может быть проблемой. - person MrFloya; 13.09.2013