使用.NET 4.0任务模式,通过HTTPClient .ReadAsAsync将JSON反序列化为数组或列表

93

我正在尝试使用.NET 4.0任务模式反序列化从http://api.usa.gov/jobs/search.json?query=nursing+jobs返回的JSON数据。 它返回以下JSON数据(在http://jsonviewer.stack.hu/上加载JSON数据)。

[
  {
    "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时,状态为Faulted。 InnerException是“Type ProjectName.Jobs is not a collection。”

我最初使用了没有JsonArray属性的Jobs类型和作为数组(Job[])的Jobs,并出现了这个错误。

  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}

如何使用.NET 4.0任务模式处理此网站的JSON?在转向.NET 4.5的await async模式之前,我想让它正常工作。

答案更新:

这里是一个演示如何使用.NET 4.5 async await模式的示例,结合了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 方法的原因。


2
你尝试过使用 ReadAsAsync<Job[]>() 吗? - svick
1
无法工作,taskwithresponse的.Result产生了这个错误。错误1:'System.Threading.Tasks.Task'不包含'Result'的定义,并且没有接受类型为'System.Threading.Tasks.Task'的第一个参数的扩展方法'Result'可以找到(您是否缺少using指令或程序集引用?) - Joe
这没有任何意义,改变ReadAsAsync()中的类型不能改变它之前的代码行为。 - svick
好的,它确实如此。它嵌入在一个ContinueWith语句中。请创建一个新的MVC4应用程序(在VS 2012中工作),并粘贴这个控制器和两个类?你能复制这个错误吗?如果可以,那么您能否建议一个经过测试的解决方案来解决问题? - Joe
3个回答

111

不要手动创建模型,尝试使用类似 Json2csharp.com 的网站。将示例 JSON 响应粘贴进去,响应越完整越好,然后导入生成的类。这样做可以减少一些繁琐的步骤,让你在 C# 中更容易处理 JSON 的形状,而且你不需要添加属性。

先让它运行起来,然后根据你的命名约定修改类名,并稍后添加属性。

编辑: 经过一番摸索,我已成功将结果反序列化为 Job 的 List(我使用了 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:

有一种方法可以在Visual Studio中使用JSON对象生成类。只需复制所选的JSON,然后转到“编辑”>“特殊粘贴”>“将JSON粘贴为类”。 这里有一个专门介绍这个功能的页面:

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


11
如果你只是同步等待异步获取数据的方法完成,那么费力去使用异步方法就毫无意义。如果你只是同步等待,最好一开始就使用同步方法。如果你想使用异步方法,那么代码应该真正地异步执行。 - Servy
2
@Joe 从哪个方面来说?他注意到代码中有些问题可以修复,使其更清晰。异步代码变得模糊不清,Servy提出了一个很好的观点。也许注释更适合,但无论如何,踩票并不一定是永久的。 - Nate-Wilkins
@cchamberlain 实际上,在C#中有同步工具来执行对网站的HTTP查询。 没有专门为您公开异步方法。 我并不是说每个应用程序都需要完全异步,只是尝试混合和匹配会给您带来很多烦恼。 如果您不打算编写异步应用程序,则不要编写异步应用程序,而是从一开始就使用同步替代方案。 - Servy
1
那第二次编辑让我很开心。谢谢! - smm
这个答案让我获得了最多的积分,但可能是付出最少努力的,有点反常。 - brumScouse
显示剩余6条评论

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

23
有人想知道扩展方法在哪里:Microsoft.AspNet.WebApi.Client NuGet包。 - Jimmyt1988
11
不要使用.Result,因为它会阻塞线程。应该使用类似这样的代码:var jsonString = await taskwithresponse.ReadAsAsync<List<Job>>() - Dave Black

7

返回类型取决于服务器,有时响应确实是一个JSON数组,但作为text/plain发送。

设置请求中的接受标头应该获取正确的类型:

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

你忘记等待对 ReadAsAsync 的调用。最后一行应该是:var model = await response.Content.ReadAsAsync<List<Job>>(); - Dave Black

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接