93

I'm trying to deserialize the JSON returned from http://api.usa.gov/jobs/search.json?query=nursing+jobs using the .NET 4.0 Task pattern. It returns this 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"
  },
  ...
]

Index Action:

  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 and Job class:

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

When I set a breakpoint on jsonTask.Wait(); and examine jsonTask the status is Faulted. The InnerException is "Type ProjectName.Jobs is not a collection."

I started with the Jobs type without the JsonArray attribute and Jobs as an array (Job[]) and got this error.

  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}

How would I process this site's JSON with the .NET 4.0 Task pattern? I would like to get this working before moving onto the await async pattern in .NET 4.5.

ANSWER UPDATE:

Here's an example using the .NET 4.5 async await pattern with brumScouse's answer.

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

You will need to bring in the System.Threading.Tasks namespace.
Note: there is no .ReadAsString method available on .Content which is why I used the .ReadAsStringAsync method.

Joe
  • 4,143
  • 8
  • 37
  • 65
  • 2
    Have you tried `ReadAsAsync()`? – svick Jun 10 '14 at 01:25
  • 1
    Doesn't work, produces this error on the .Result on taskwithresponse. Error 1 'System.Threading.Tasks.Task' does not contain a definition for 'Result' and no extension method 'Result' accepting a first argument of type 'System.Threading.Tasks.Task' could be found (are you missing a using directive or an assembly reference?) – Joe Jun 10 '14 at 04:42
  • That doesn't make any sense, changing the type in `ReadAsAsync()` can't change how the code before it behaves. – svick Jun 10 '14 at 10:16
  • Well it did. It is in embedded in a ContinueWith statement. Please create a new MVC4 application (working in VS 2012) and paste in this controller and two classes? Can you replicate the error? If so are you then able to suggest a tested solution that fixes the problem? – Joe Jun 10 '14 at 16:34

3 Answers3

111

Instead of handcranking your models try using something like the Json2csharp.com website. Paste In an example JSON response, the fuller the better and then pull in the resultant generated classes. This, at least, takes away some moving parts, will get you the shape of the JSON in csharp giving the serialiser an easier time and you shouldnt have to add attributes.

Just get it working and then make amendments to your class names, to conform to your naming conventions, and add in attributes later.

EDIT: Ok after a little messing around I have successfully deserialised the result into a List of Job (I used Json2csharp.com to create the class for me)

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

And an edit to your 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();

This means you can get rid of your containing object. Its worth noting that this isn't a Task related issue but rather a deserialisation issue.

EDIT 2:

There is a way to take a JSON object and generate classes in Visual Studio. Simply copy the JSON of choice and then Edit> Paste Special > Paste JSON as Classes. A whole page is devoted to this here:

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

brumScouse
  • 3,166
  • 1
  • 24
  • 38
  • I used Fiddler to discover the JSON and it showed that there are multiple {}'s returned. Json2csharp.com is a nice tool for what it does but it did not show that there were multiple {}'s. I've successfully used the JsonProperty attribute with other URL's so my problem is not with the JsonProperty attributes but with how to deserialize the JSON returned into an array or list with the Task pattern in .NET 4.0. – Joe Jun 10 '14 at 17:12
  • Yes, as I said from the beginning this was a JSON de-serialization issue. Capturing the string and then JsonConverting did the trick and it now works. No need to modify the Job type, I retained my original Job type with the JsonProperty attributes. – Joe Jun 10 '14 at 20:20
  • 11
    There's no point in going out of your way to use asynchronous methods of fetching the data if you're just going to syncrhonously wait for them to finish. If you're just going to synchronously wait, you may as well just use synchronous methods from the start. If you are going to use asynchronous methods, the code should actually be asynchronous. – Servy Jun 10 '14 at 21:17
  • Servy I assume you are the one that downvoted. If so that was inappropriate and mean spirited. brumScouse actually provided a tested and working answer to the original question. The question had nothing to do with the advisability of using async methods but how if you choose to do so. Your comment is completely off-track. – Joe Jun 10 '14 at 21:26
  • 2
    @Joe In what regard? He noticed something was odd about the code that could be fixed, make it clearer. Asynchronous code gets cloudy and Servy makes a good point. Maybe a comment was better suited but either way downvotes don't have to be permanent. – Nate-Wilkins Oct 01 '14 at 02:24
  • @Servy - HttpClient offers no synchronous counterparts. While async is always preferred, there are still many tasks (legacy) where it cannot be used. One of the things I use this code for is making a call from a legacy classic ASP server to .net web api within a COM component. – cchamberlain Sep 10 '16 at 00:31
  • @cchamberlain There are in fact synchronous tools in C# to perform an HTTP query to a website. There are not exclusively asynchronous methods exposed to you. I'm not saying every application needs to be entirely asynchronous, merely that it's trying to mix and match that will cause you a lot of grief. If you aren't going to write an asynchronous application, then *don't write an asynchronous application* and just use synchronous alternatives from the start. – Servy Sep 10 '16 at 01:33
  • @Servy - It makes sense if you are not planning to ever use async in your application, to not use async clients. I'm not sure what the use case for that is with almost all current tech supporting async. If I am writing multiple clients targeting an API that supports async / non-async, and some of my clients don't support async, I'd still rather share code and simply wrap the non-supporting clients with code similiar to this answer. – cchamberlain Sep 10 '16 at 01:44
  • @cchamberlain And that's going to cause you lots of problems. You should stop doing that. Asynchronous code is designed to be used in entirely asynchronous applications. When you mix and match you're going to cause yourself problems. It makes deadlocks easy, adds lots of unnecessary overhead, etc. – Servy Sep 11 '16 at 23:46
  • 1
    That 2nd Edit just made my day. Thanks! – smm Aug 23 '17 at 18:31
  • This answer has give me my most points but was probably the least amount of effort thats a bit perverse. – brumScouse Sep 26 '17 at 13:31
23
var response = taskwithresponse.Result;
          var jsonString = response.ReadAsAsync<List<Job>>().Result;
Veera Induvasi
  • 822
  • 7
  • 19
  • 22
    For anyone wondering where the extension method is: Microsoft.AspNet.WebApi.Client nuget package. – Jimmyt1988 Nov 01 '16 at 16:05
  • 11
    Should never use `.Result` since it blocks the thread. Should use something like: `var jsonString = await taskwithresponse.ReadAsAsync>()` – Dave Black Dec 31 '19 at 23:41
7

The return type depends on the server, sometimes the response is indeed a JSON array but sent as text/plain

Setting the accept headers in the request should get the correct type:

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

which can then be serialized to a JSON list or array. Thanks for the comment from @svick which made me curious that it should work.

The Exception I got without configuring the accept headers was System.Net.Http.UnsupportedMediaTypeException.

Following code is cleaner and should work (untested, but works in my case):

    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>>();
Silis Alin
  • 140
  • 2
  • 13
Thomas
  • 2,127
  • 1
  • 32
  • 45
  • you forgot to await the call to `ReadAsAsync`. The last line should read: `var model = await response.Content.ReadAsAsync>();` – Dave Black Dec 31 '19 at 23:42