Десериализовать JSON в массив или список с HTTPClient .ReadAsAsync, используя шаблон задачи .NET 4.0

Я пытаюсь десериализовать JSON, возвращенный из http://api.usa.gov/jobs/search.json?query=nursing+jobs, с помощью шаблона задач .NET 4.0. Он возвращает этот JSON («Загрузить данные JSON» @ http://jsonviewer.stack.hu/).

[
  {
    "id": "usajobs:353400300",
    "position_title": "Nurse",
    "organization_name": "Indian Health Service",
    "rate_interval_code": "PA",
    "minimum": 42492,
    "maximum": 61171,
    "start_date": "2013-10-01",
    "end_date": "2014-09-30",
    "locations": [
      "Gallup, NM"
    ],
    "url": "https://www.usajobs.gov/GetJob/ViewDetails/353400300"
  },
  {
    "id": "usajobs:359509200",
    "position_title": "Nurse",
    "organization_name": "Indian Health Service",
    "rate_interval_code": "PA",
    "minimum": 42913,
    "maximum": 61775,
    "start_date": "2014-01-16",
    "end_date": "2014-12-31",
    "locations": [
      "Gallup, NM"
    ],
    "url": "https://www.usajobs.gov/GetJob/ViewDetails/359509200"
  },
  ...
]

Индекс Действие:

  public class HomeController : Controller
  {
    public ActionResult Index()
    {
      Jobs model = null;
      var client = new HttpClient();
      var task = client.GetAsync("http://api.usa.gov/jobs/search.json?query=nursing+jobs")
        .ContinueWith((taskwithresponse) =>
        {
          var response = taskwithresponse.Result;
          var jsonTask = response.Content.ReadAsAsync<Jobs>();
          jsonTask.Wait();
          model = jsonTask.Result;
        });
      task.Wait();
      ...
     }

Вакансии и класс работы:

  [JsonArray]
  public class Jobs { public List<Job> JSON; }

  public class Job
  {
    [JsonProperty("organization_name")]
    public string Organization { get; set; }
    [JsonProperty("position_title")]
    public string Title { get; set; }
  }

Когда я устанавливаю точку останова на jsonTask.Wait(); и проверяю jsonTask, статус - «Ошибка». InnerException: «Тип ProjectName.Jobs не является коллекцией».

Я начал с типа Jobs без атрибута JsonArray и Jobs в виде массива (Job []) и получил эту ошибку.

  public class Jobs { public Job[] JSON; }

    +       InnerException  {"Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'ProjectName.Models.Jobs' because the type requires a JSON object (e.g. {\"name\":\"value\"}) to deserialize correctly.\r\n
    To fix this error either change the JSON to a JSON object (e.g. {\"name\":\"value\"}) or change the deserialized type to an array or a type that implements a collection interface
 (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.\r\n
Path '', line 1, position 1."}  System.Exception {Newtonsoft.Json.JsonSerializationException}

Как мне обработать JSON этого сайта с помощью шаблона задач .NET 4.0? Я хотел бы, чтобы это работало, прежде чем переходить к шаблону await async в .NET 4.5.

ОБНОВЛЕНИЕ ОТВЕТА:

Вот пример использования шаблона async await .NET 4.5 с ответом brumScouse.

 public async Task<ActionResult>Index()
 {
    List<Job> model = null;
    var client = newHttpClient();

    // .NET 4.5 async await pattern
    var task = await client.GetAsync(http://api.usa.gov/jobs/search.json?query=nursing+jobs);
    var jsonString = await task.Content.ReadAsStringAsync();
    model = JsonConvert.DeserializeObject<List<Job>>(jsonString);
    returnView(model);
 }

Вам нужно будет ввести пространство имен System.Threading.Tasks.
Примечание: на .Content нет метода .ReadAsString, поэтому я использовал метод .ReadAsStringAsync.


person Joe    schedule 10.06.2014    source источник
comment
Вы пробовали ReadAsAsync<Job[]>()?   -  person svick    schedule 10.06.2014
comment
Не работает, выдает эту ошибку в .Result on taskwithresponse. Ошибка 1 «System.Threading.Tasks.Task» не содержит определения для «Result», и не может быть найден метод расширения «Result», принимающий первый аргумент типа «System.Threading.Tasks.Task» (если вам не хватает директиву using или ссылку на сборку?)   -  person Joe    schedule 10.06.2014
comment
В этом нет никакого смысла, изменение типа в ReadAsAsync() не может изменить поведение кода до того, как он поведет себя.   -  person svick    schedule 10.06.2014
comment
Хорошо, что это так. Он встроен в оператор ContinueWith. Создайте новое приложение MVC4 (работающее в VS 2012) и вставьте этот контроллер и два класса? Можете ли вы воспроизвести ошибку? Если да, то можете ли вы предложить проверенное решение, которое устраняет проблему?   -  person Joe    schedule 10.06.2014


Ответы (3)


Вместо того, чтобы запускать свои модели вручную, попробуйте использовать что-то вроде веб-сайта Json2csharp.com. Вставить. В примере ответа JSON, чем полнее, тем лучше, а затем вставьте полученные в результате сгенерированные классы. Это, по крайней мере, убирает некоторые движущиеся части, дает вам форму JSON в csharp, упрощая сериализатор, и вам не нужно добавлять атрибуты.

Просто заставьте его работать, а затем внесите поправки в имена классов, чтобы они соответствовали вашим соглашениям об именах, и добавьте атрибуты позже.

РЕДАКТИРОВАТЬ: Хорошо, после небольшого возни, я успешно десериализовал результат в список заданий (я использовал Json2csharp.com для создания класса для меня)

public class Job
{
        public string id { get; set; }
        public string position_title { get; set; }
        public string organization_name { get; set; }
        public string rate_interval_code { get; set; }
        public int minimum { get; set; }
        public int maximum { get; set; }
        public string start_date { get; set; }
        public string end_date { get; set; }
        public List<string> locations { get; set; }
        public string url { get; set; }
}

И отредактируйте ваш код:

        List<Job> model = null;
        var client = new HttpClient();
        var task = client.GetAsync("http://api.usa.gov/jobs/search.json?query=nursing+jobs")
          .ContinueWith((taskwithresponse) =>
          {
              var response = taskwithresponse.Result;
              var jsonString = response.Content.ReadAsStringAsync();
              jsonString.Wait();
              model = JsonConvert.DeserializeObject<List<Job>>(jsonString.Result);

          });
        task.Wait();

Это означает, что вы можете избавиться от содержащего объект объекта. Стоит отметить, что это не проблема, связанная с задачей, а проблема десериализации.

РЕДАКТИРОВАТЬ 2:

Есть способ взять объект JSON и сгенерировать классы в Visual Studio. Просто скопируйте выбранный JSON, а затем выберите Edit> Paste Special> Paste JSON as Classes. Этому здесь посвящена целая страница:

http://blog.codeinside.eu/2014/09/08/Visual-Studio-2013-Paste-Special-JSON-And-Xml/

person brumScouse    schedule 10.06.2014
comment
Я использовал Fiddler для обнаружения JSON, и он показал, что было возвращено несколько {}. Json2csharp.com - хороший инструмент для того, что он делает, но он не показал, что было несколько {}. Я успешно использовал атрибут JsonProperty с другими URL-адресами, поэтому моя проблема не в атрибутах JsonProperty, а в том, как десериализовать JSON, возвращенный в массив или список с шаблоном задачи в .NET 4.0. - person Joe; 10.06.2014
comment
Да, как я сказал с самого начала, это была проблема десериализации JSON. Захват строки, а затем JsonConverting сделали свое дело, и теперь он работает. Нет необходимости изменять тип задания, я сохранил свой исходный тип задания с атрибутами JsonProperty. - person Joe; 11.06.2014
comment
Нет смысла отказываться от использования асинхронных методов выборки данных, если вы просто собираетесь синхронно ждать их завершения. Если вы просто собираетесь синхронно ждать, вы можете просто использовать синхронные методы с самого начала. Если вы собираетесь использовать асинхронные методы, код действительно должен быть асинхронным. - person Servy; 11.06.2014
comment
Servy Я полагаю, вы проголосовали против. Если так, это было неуместно и подло. brumScouse фактически предоставил проверенный и рабочий ответ на исходный вопрос. Вопрос не имел ничего общего с целесообразностью использования асинхронных методов, но как, если вы решите это сделать. Ваш комментарий совершенно не соответствует действительности. - person Joe; 11.06.2014
comment
@ Джо В каком отношении? Он заметил что-то странное в коде, которое можно исправить, сделать его более понятным. Асинхронный код становится непонятным, и Серви хорошо замечает. Возможно, комментарий был бы лучше подходящим, но в любом случае голоса против не обязательно должны быть постоянными. - person Nate-Wilkins; 01.10.2014
comment
@Servy - HttpClient не предлагает синхронных аналогов. Хотя асинхронный режим всегда предпочтительнее, есть еще много задач (устаревших), где его нельзя использовать. Одна из вещей, для которых я использую этот код, - это вызов устаревшего классического сервера ASP на .net web api в компоненте COM. - person cchamberlain; 10.09.2016
comment
@cchamberlain На самом деле в C # есть синхронные инструменты для выполнения HTTP-запроса к веб-сайту. Вам доступны не только асинхронные методы. Я не говорю, что каждое приложение должно быть полностью асинхронным, просто оно пытается смешивать и сочетать, что доставит вам много неприятностей. Если вы не собираетесь писать асинхронное приложение, тогда не пишите асинхронное приложение и просто используйте синхронные альтернативы с самого начала. - person Servy; 10.09.2016
comment
@Servy - если вы не планируете когда-либо использовать async в своем приложении, имеет смысл не использовать async-клиентов. Я не уверен, как это можно использовать, поскольку почти все современные технологии поддерживают асинхронность. Если я пишу несколько клиентов, ориентированных на API, который поддерживает асинхронный / неасинхронный режим, а некоторые из моих клиентов не поддерживают асинхронный режим, я бы предпочел поделиться кодом и просто обернуть не поддерживающих клиентов кодом, похожим на этот ответ. - person cchamberlain; 10.09.2016
comment
@cchamberlain И это доставит вам много проблем. Тебе следует прекратить это делать. Асинхронный код предназначен для использования в полностью асинхронных приложениях. Когда вы смешиваете и сочетаете, вы создаете себе проблемы. Это упрощает взаимоблокировки, добавляет много ненужных накладных расходов и т. Д. - person Servy; 12.09.2016
comment
Этот 2-й Edit просто сделал мой день. Спасибо! - person smm; 23.08.2017
comment
Этот ответ дал мне больше всего очков, но, вероятно, это было наименьшее количество усилий, которые немного извращены. - person brumScouse; 26.09.2017

Тип возврата зависит от сервера, иногда ответ действительно представляет собой массив JSON, но отправляется как текстовый / простой

Установка заголовков принятия в запросе должна иметь правильный тип:

client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); 

который затем может быть сериализован в список или массив JSON. Спасибо за комментарий от @svick, который заставил меня задуматься о том, что он должен работать.

Исключением, которое я получил без настройки заголовков принятия, было System.Net.Http.UnsupportedMediaTypeException.

Следующий код более чистый и должен работать (не тестировался, но работает в моем случае):

    var client = new HttpClient();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    var response = await client.GetAsync("http://api.usa.gov/jobs/search.json?query=nursing+jobs");
    var model = await response.Content.ReadAsAsync<List<Job>>();
person Thomas    schedule 02.03.2017
comment
вы забыли дождаться звонка на ReadAsAsync. Последнюю строку следует читать: var model = await response.Content.ReadAsAsync<List<Job>>(); - person Dave Black; 01.01.2020

person    schedule
comment
Для всех, кто интересуется, где находится метод расширения: пакет Nuget Microsoft.AspNet.WebApi.Client. - person Jimmyt1988; 01.11.2016
comment
Никогда не следует использовать .Result, поскольку он блокирует поток. Следует использовать что-то вроде: var jsonString = await taskwithresponse.ReadAsAsync<List<Job>>() - person Dave Black; 01.01.2020