Deserialisieren Sie JSON mit HTTPClient .ReadAsAsync mithilfe des .NET 4.0-Taskmusters in Array oder Liste

77

Ich versuche, den JSON zu deserialisieren, der von der http://api.usa.gov/jobs/search.json?query=nursing+jobsVerwendung des .NET 4.0-Taskmusters zurückgegeben wurde. Es gibt diesen JSON zurück ('JSON-Daten laden' @ 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"
  },
  ...
]

Indexaktion:

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

Jobs und Jobklasse:

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

Wenn ich einen Haltepunkt einstelle jsonTask.Wait();und prüfe, ist jsonTaskder Status Fehler. Die InnerException lautet "Typ ProjectName.Jobs ist keine Sammlung."

Ich habe mit dem Jobtyp ohne das JsonArray-Attribut und Jobs als Array (Job []) begonnen und diesen Fehler erhalten.

  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}

Wie würde ich den JSON dieser Site mit dem .NET 4.0-Taskmuster verarbeiten? Ich möchte dies zum Laufen bringen, bevor ich mit dem await asyncMuster in .NET 4.5 fortfahre.

ANTWORT-UPDATE:

Hier ist ein Beispiel für die Verwendung des asynchronen Wartemusters .NET 4.5 mit der Antwort von 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);
 }

Sie müssen den System.Threading.TasksNamespace einbringen .
Hinweis: Es ist keine .ReadAsStringMethode verfügbar .Content, weshalb ich die .ReadAsStringAsyncMethode verwendet habe.

Joe
quelle
2
Hast du es versucht ReadAsAsync<Job[]>()?
Svick
1
Funktioniert nicht, erzeugt diesen Fehler im .Result on taskwithresponse. Fehler 1 'System.Threading.Tasks.Task' enthält keine Definition für 'Ergebnis' und keine Erweiterungsmethode 'Ergebnis', die ein erstes Argument vom Typ 'System.Threading.Tasks.Task' akzeptiert. Es wurde gefunden (fehlt Ihnen ein mit Direktive oder einer Assembly-Referenz?)
Joe
Das macht keinen Sinn, das Ändern des Typs ReadAsAsync()kann nicht ändern, wie sich der Code verhält, bevor er sich verhält.
Svick
Nun, das hat es getan. Es ist in eine ContinueWith-Anweisung eingebettet. Bitte erstellen Sie eine neue MVC4-Anwendung (funktioniert in VS 2012) und fügen Sie diesen Controller und zwei Klassen ein. Können Sie den Fehler replizieren? Wenn ja, können Sie dann eine getestete Lösung vorschlagen, die das Problem behebt?
Joe

Antworten:

98

Versuchen Sie, die Modelle von Json2csharp.com zu verwenden, anstatt Ihre Modelle von Hand anzukurbeln. Einfügen In einer Beispiel-JSON-Antwort gilt: Je voller, desto besser und ziehen Sie die resultierenden generierten Klassen ein. Dadurch werden zumindest einige bewegliche Teile entfernt, und Sie erhalten die Form des JSON in csharp, wodurch der Serialisierer einfacher wird und Sie keine Attribute hinzufügen müssen.

Lassen Sie es einfach funktionieren und nehmen Sie dann Änderungen an Ihren Klassennamen vor, um Ihren Namenskonventionen zu entsprechen, und fügen Sie später Attribute hinzu.

EDIT: Ok, nach einigem Herumspielen habe ich das Ergebnis erfolgreich in eine Jobliste deserialisiert (ich habe Json2csharp.com verwendet, um die Klasse für mich zu erstellen).

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

Und eine Bearbeitung Ihres Codes:

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

Dies bedeutet, dass Sie Ihr enthaltenes Objekt entfernen können. Es ist erwähnenswert, dass dies kein aufgabenbezogenes Problem ist, sondern ein Deserialisierungsproblem.

EDIT 2:

Es gibt eine Möglichkeit, ein JSON-Objekt zu nehmen und Klassen in Visual Studio zu generieren. Kopieren Sie einfach den JSON Ihrer Wahl und dann Bearbeiten> Spezial einfügen> JSON als Klassen einfügen. Hier ist eine ganze Seite dazu gewidmet:

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

brumScouse
quelle
Ich habe Fiddler verwendet, um den JSON zu entdecken, und es hat gezeigt, dass mehrere {} zurückgegeben werden. Json2csharp.com ist ein nettes Tool für das, was es tut, aber es hat nicht gezeigt, dass es mehrere {} gibt. Ich habe das JsonProperty-Attribut erfolgreich mit anderen URLs verwendet, daher besteht mein Problem nicht in den JsonProperty-Attributen, sondern darin, wie der in ein Array oder eine Liste mit dem Task-Muster in .NET 4.0 zurückgegebene JSON deserialisiert wird.
Joe
Ja, wie ich von Anfang an sagte, war dies ein JSON-De-Serialisierungsproblem. Das Erfassen des Strings und dann JsonConverting hat den Trick gemacht und es funktioniert jetzt. Der Jobtyp muss nicht geändert werden. Ich habe meinen ursprünglichen Jobtyp mit den JsonProperty-Attributen beibehalten.
Joe
9
Es macht keinen Sinn, sich die Mühe zu machen, asynchrone Methoden zum Abrufen der Daten zu verwenden, wenn Sie nur synchron warten, bis sie fertig sind. Wenn Sie nur synchron warten möchten, können Sie auch von Anfang an synchrone Methoden verwenden. Wenn Sie asynchrone Methoden verwenden, sollte der Code tatsächlich asynchron sein.
Servy
2
@ Joe In welcher Hinsicht? Er bemerkte, dass etwas an dem Code seltsam war, das repariert werden konnte, um es klarer zu machen. Asynchroner Code wird trübe und Servy macht einen guten Punkt. Vielleicht war ein Kommentar besser geeignet, aber so oder so müssen Abstimmungen nicht dauerhaft sein.
Nate-Wilkins
1
Diese 2. Bearbeitung hat mir gerade den Tag gemacht. Vielen Dank!
smm
19
var response = taskwithresponse.Result;
          var jsonString = response.ReadAsAsync<List<Job>>().Result;
Veera Induvasi
quelle
17
Für alle, die sich fragen, wo sich die Erweiterungsmethode befindet: Microsoft.AspNet.WebApi.Client-Nuget-Paket.
Jimmyt1988
5
Sollte niemals verwendet werden, .Resultda es den Thread blockiert. Sollte etwas verwenden wie:var jsonString = await taskwithresponse.ReadAsAsync<List<Job>>()
Dave Black
5

Der Rückgabetyp hängt vom Server ab. Manchmal ist die Antwort zwar ein JSON-Array, wird jedoch als Text / Nur gesendet

Das Setzen der Accept-Header in der Anfrage sollte den richtigen Typ erhalten:

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

Diese können dann in eine JSON-Liste oder ein JSON-Array serialisiert werden. Vielen Dank für den Kommentar von @svick, der mich neugierig gemacht hat, dass es funktionieren sollte.

Die Ausnahme, die ich ohne Konfiguration der Accept-Header erhalten habe, war System.Net.Http.UnsupportedMediaTypeException.

Der folgende Code ist sauberer und sollte funktionieren (ungetestet, funktioniert aber in meinem Fall):

    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>>();
Thomas
quelle
Sie haben vergessen, auf den Anruf zu warten ReadAsAsync. Die letzte Zeile sollte lauten:var model = await response.Content.ReadAsAsync<List<Job>>();
Dave Black