web-dev-qa-db-fra.com

Désérialiser JSON en tableau ou en liste avec HTTPClient .ReadAsAsync à l'aide du modèle de tâche .NET 4.0

J'essaie de désérialiser le code JSON renvoyé par http://api.usa.gov/jobs/search.json?query=nursing+jobs à l'aide du modèle de tâche .NET 4.0. Il retourne ce JSON ('Load JSON data' @ 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"
  },
  ...
]

Action indexée:

  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();
      ...
     }

Emplois et classe d'emploi:

  [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; }
  }

Lorsque je définis un point d'arrêt sur jsonTask.Wait(); et que j'examine jsonTask l'état est Faulted. L'exception InnerException est "Type NomProjet.Jobs n'est pas une collection".

J'ai commencé avec le type Jobs sans l'attribut JsonArray et Jobs en tant que tableau (Job []) et j'ai obtenu cette erreur.

  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}

Comment traiter le JSON de ce site avec le modèle de tâche .NET 4.0? J'aimerais que cela fonctionne avant de passer au modèle await async dans .NET 4.5.

RÉPONSE MISE À JOUR:

Voici un exemple d'utilisation du modèle d'attente asynchrone .NET 4.5 avec la réponse de 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);
 }

Vous devrez importer l'espace de noms System.Threading.Tasks.
Remarque: Il n'y a pas de méthode .ReadAsString disponible sur .Content, c'est pourquoi j'ai utilisé la méthode .ReadAsStringAsync.

66
Joe

Au lieu de lancer manuellement vos modèles, essayez d’utiliser quelque chose comme le site Web Json2csharp.com. Coller Dans un exemple de réponse JSON, le plus complet sera le mieux, puis extraire les classes générées résultantes. Cela enlève au moins quelques pièces mobiles, vous obtiendrez la forme du JSON dans csharp, ce qui simplifiera la tâche du sérialiseur et vous ne devrez pas ajouter d’attributs.

Faites-le simplement fonctionner, puis apportez des modifications à vos noms de classe, afin de vous conformer à vos conventions de dénomination, et ajoutez des attributs ultérieurement.

EDIT: Ok après un petit désordre, j'ai réussi à désérialiser le résultat en une liste de tâches (j'ai utilisé Json2csharp.com pour créer la classe à ma place).

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; }
}

Et une modification de votre code:

        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();

Cela signifie que vous pouvez vous débarrasser de votre objet contenant. Il est intéressant de noter qu'il ne s'agit pas d'un problème lié à la tâche mais plutôt d'un problème de désérialisation.

EDIT 2:

Il existe un moyen de prendre un objet JSON et de générer des classes dans Visual Studio. Copiez simplement le JSON de votre choix, puis Edition> Collage spécial> Coller JSON en tant que classes. Une page entière est consacrée à cela ici:

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

86
brumScouse
var response = taskwithresponse.Result;
          var jsonString = response.ReadAsAsync<List<Job>>().Result;
17
Veera Induvasi

Le type de retour dépend du serveur. Parfois, la réponse est effectivement un tableau JSON, mais elle est envoyée sous la forme text/plain.

La définition des en-têtes d'acceptation dans la demande doit avoir le type correct:

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

qui peuvent ensuite être sérialisés dans une liste ou un tableau JSON. Merci pour le commentaire de @svick qui m'a rendu curieux que cela fonctionne.

L'exception que j'ai eu sans configurer les en-têtes d'acceptation était System.Net.Http.UnsupportedMediaTypeException.

Le code suivant est plus propre et devrait fonctionner (non testé, mais fonctionne dans mon cas):

    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 = response.Content.ReadAsAsync<List<Job>>();
3
Thomas