Как я могу использовать ReadAsAsync‹T› с этой схемой данных?

Я использую System.Net.Http.HttpClient, версию, доступную в настоящее время в NuGet, для извлечения данных из сервис в формате json. Данные примерно выглядят так:

{
  "schema": "Listing",
  "data": {
    "key": "28ba648c-de24-45d4-a7d9-70f810cf5438",
    "children": [{
      "kind": "type1",
      "data": {
        "body": "Four score and seven years ago...",
        "parent_id": "2qh3l",
        "report_count": 0,
        "name": "c4j6yeh"
      }
    }, {
      "kind": "type3",
      "data": {
        "domain": "abc.def.com",
        "flagged": true,
        "category": "news",
        "saved": false,
        "id": "t3dz0",
        "created": 1335998011.0
        }
    }]
  }
}

Я использую HttpContentExtensions.ReadAsAsync<T> для десериализации эту строку json в граф объектов. Определения типов выглядят примерно так:

public class Response
{
    public String schema { get;set; }
    public ListingData data { get;set; }
}

public class ListingData
{
    public string key { get;set; }
    public List<OneItem> children { get;set; }
}

Вот проблема: я хочу, чтобы тип элементов в children менялся в зависимости от свойства kind. Если kind является "type1", то я хочу десериализовать объект... назовем его Type1 . Если kind имеет тип 3, то мне нужен объект типа Type3.

Прямо сейчас я могу десериализовать List<Type1> или List<Type3>, но я не знаю, как заставить логику десериализации различать их.

Я мог бы объединить все свойства объекта данных "type1" и объекта данных "type3" в один тип .NET. Но количество свойств достаточно велико, и это становится беспорядочным.

Если бы имя свойства в JSON (в данном случае data) было другим, я мог бы отличить его. Если бы, например, данные выглядели так:

    "children": [{
      "kind": "type1",
      "t1data": { ... }
    }, {
      "kind": "type3",
      "t3data": { ... }
    }]

... тогда я мог бы сделать что-то подобное в .NET:

public class OneItem
{
    public string kind { get;set; }
    public Type1 t1data { get;set; }
    public Type3 t3data { get;set; }
}

Но моя схема данных не выглядит так.

Можно ли выбрать тип десериализации по содержанию данных? Другими словами, просмотрите значение одного свойства (в данном случае kind), чтобы определить, как десериализовать содержимое для другого свойства (в данном случае data).

Или можно внедрить фильтр или преобразователь, который воздействует на JSON до того, как ReadAsAsync попытается его десериализовать?

Если да, то как?


person Cheeso    schedule 02.05.2012    source источник


Ответы (1)


Если вы согласны с предварительной обработкой своего ответа и можете использовать Json.NET, вы сможете делать то, что хотите.

Учитывая следующие классы:

public class Response
{
    public string schema
    {
        get;
        set;
    }

    public ListingData data
    {
        get;
        set;
    }
}

public class ListingData
{
    public string key
    {
        get;
        set;
    }

    public List<object> children
    {
        get;
        set;
    }
}

public class Type1
{
    public string body
    {
        get;
        set;
    }

    public string parent_id
    {
        get;
        set;
    }

    public int report_count
    {
        get;
        set;
    }

    public string name
    {
        get;
        set;
    }
}

public class Type3
{
    public string domain
    {
        get;
        set;
    }

    public bool flagged
    {
        get;
        set;
    }

    public string category
    {
        get;
        set;
    }

    public bool saved
    {
        get;
        set;
    }

    public string id
    {
        get;
        set;
    }

    public double created
    {
        get;
        set;
    }
}

Этот тест проходит:

[Test]
public void RoundTrip()
{
    var response = new Response
                        {
                            schema = "Listing",
                            data = new ListingData
                                        {
                                            key = "28ba648c-de24-45d4-a7d9-70f810cf5438",
                                            children = new List<object>
                                                            {
                                                                new Type1
                                                                    {
                                                                        body = "Four score and seven years ago...",
                                                                        parent_id = "2qh3l",
                                                                        report_count = 0,
                                                                        name = "c4j6yeh"
                                                                    },
                                                                new Type3
                                                                    {
                                                                        domain = "abc.def.com",
                                                                        flagged = true,
                                                                        category = "news",
                                                                        saved = false,
                                                                        id = "t3dz0",
                                                                        created = 1335998011.0
                                                                    }
                                                            }
                                        }
                        };

    var jsonSerializerSettings = new JsonSerializerSettings
                                        {
                                            Formatting = Formatting.Indented,
                                            TypeNameHandling = TypeNameHandling.Objects
                                        };

    string serializedResponse = JsonConvert.SerializeObject(response, jsonSerializerSettings);
    Console.WriteLine(serializedResponse);
    var roundTrippedResponse = JsonConvert.DeserializeObject<Response>(serializedResponse, jsonSerializerSettings);
    Assert.That(roundTrippedResponse.data.children.First().GetType(), Is.EqualTo(typeof(Type1)));
    Assert.That(roundTrippedResponse.data.children.Last().GetType(), Is.EqualTo(typeof(Type3)));
}

Вывод, записанный в консоль:

{
  "$type": "Test.Response, Test",
  "schema": "Listing",
  "data": {
    "$type": "Test.ListingData, Test",
    "key": "28ba648c-de24-45d4-a7d9-70f810cf5438",
    "children": [
      {
        "$type": "Test.Type1, Test",
        "body": "Four score and seven years ago...",
        "parent_id": "2qh3l",
        "report_count": 0,
        "name": "c4j6yeh"
      },
      {
        "$type": "Test.Type3, Test",
        "domain": "abc.def.com",
        "flagged": true,
        "category": "news",
        "saved": false,
        "id": "t3dz0",
        "created": 1335998011.0
      }
    ]
  }
}

Поэтому, если вы можете преобразовать полученный ответ в соответствии с ожидаемым форматом Json.NET, это сработает.

Чтобы собрать все это воедино, вам потребуется написать собственный MediaTypeFormatter и передать его вызову ReadAsAsync‹>().

person David Peden    schedule 14.05.2012