Десеріалізуйте JSON до масиву або списку за допомогою HTTPClient .ReadAsAsync за допомогою шаблону завдань .NET 4.0


77

Я намагаюся десериалізувати 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.

ОНОВЛЕННЯ ВІДПОВІДІ:

Ось приклад використання асинхронного шаблону очікування .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простір імен.
Примітка: немає .ReadAsStringдоступного методу, .Contentсаме тому я використав цей .ReadAsStringAsyncметод.


2
Ви пробували ReadAsAsync<Job[]>()?
svick

1
Не працює, видає цю помилку в .Result on taskwithresponse. Помилка 1 "System.Threading.Tasks.Task" не містить визначення "Result" і не може бути знайдено жодного методу розширення "Result", що приймає перший аргумент типу "System.Threading.Tasks.Task" (вам не вистачає за допомогою директиви чи посилання на збірку?)
Джо

Це не має сенсу, зміна типу в ReadAsAsync()не може змінити поведінку коду до того, як він поводиться.
svick

Ну це зробили. Це вбудовано в оператор ContinueWith. Будь ласка, створіть нову програму MVC4 (працює у VS 2012) та вставте в цей контролер та два класи? Чи можете ви повторити помилку? Якщо так, то чи можете ви запропонувати перевірене рішення, яке усуває проблему?
Джо

Відповіді:


98

Замість ручного викручування своїх моделей спробуйте використати щось на зразок веб-сайту 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, а потім "Редагувати"> "Спеціальна вставка"> "Вставити JSON як класи". Цьому тут присвячена ціла сторінка:

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


Я використав Fiddler, щоб виявити JSON, і він показав, що повернуто кілька {}. Json2csharp.com - хороший інструмент для того, що він робить, але він не показав, що було кілька {}. Я успішно використовував атрибут JsonProperty з іншими URL-адресами, тому моя проблема не в атрибутах JsonProperty, а в тому, як десериалізувати JSON, повернутий у масив або список із шаблоном Завдання в .NET 4.0.
Джо

Так, як я вже говорив з самого початку, це було питання десериалізації JSON. Захоплення рядка, а потім JsonConverting зробив свою справу, і це тепер працює. Не потрібно змінювати тип завдання, я зберіг свій початковий тип завдання з атрибутами JsonProperty.
Джо

9
Немає сенсу намагатись використовувати асинхронні методи отримання даних, якщо ви просто синхронно чекаєте їх закінчення. Якщо ви просто збираєтесь синхронно чекати, ви можете також просто використовувати синхронні методи з самого початку. Якщо ви збираєтеся використовувати асинхронні методи, код насправді повинен бути асинхронним.
Серві

2
@Joe Щодо цього? Він помітив, що в коді є щось дивне, що можна виправити, зробити його зрозумілішим. Асинхронний код стає похмурим, і Серві робить хороший результат. Можливо, коментар більше підходить, але в будь-якому випадку голоси проти не повинні бути постійними.
Nate-Wilkins

1
Це друге редагування щойно зробило мій день. Дякую!
smm

19
var response = taskwithresponse.Result;
          var jsonString = response.ReadAsAsync<List<Job>>().Result;

17
Для тих, хто цікавиться, де метод розширення: пакет nuget Microsoft.AspNet.WebApi.Client.
Jimmyt1988

6
Ніколи не слід використовувати, .Resultоскільки це блокує потік. Потрібно використовувати щось на кшталт:var jsonString = await taskwithresponse.ReadAsAsync<List<Job>>()
Дейв Блек

5

Тип повернення залежить від сервера, іноді відповідь справді є масивом JSON, але надсилається як text / plain

Встановлення заголовків accept у запиті має отримати правильний тип:

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

які потім можна серіалізувати до списку або масиву JSON. Дякую за коментар від @svick, який викликав у мене запитання, що це має спрацювати.

Винятком, який я отримав без налаштування заголовків accept, було 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>>();

ви забули дочекатися дзвінка ReadAsAsync. В останньому рядку слід читати:var model = await response.Content.ReadAsAsync<List<Job>>();
Дейв Блек
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.