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

Posted

技术标签:

【中文标题】使用 .NET 4.0 任务模式使用 HTTPClient .ReadAsAsync 将 JSON 反序列化为数组或列表【英文标题】:Deserialize JSON to Array or List with HTTPClient .ReadAsAsync using .NET 4.0 Task pattern 【发布时间】:2014-06-10 00:27:49 【问题描述】:

我正在尝试使用 .NET 4.0 任务模式反序列化从 http://api.usa.gov/jobs/search.json?query=nursing+jobs 返回的 JSON。它返回这个 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 不是一个集合。”

我从没有 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 异步等待模式和 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 方法的原因。

【问题讨论】:

你试过ReadAsAsync&lt;Job[]&gt;()吗? 不起作用,在 taskwithresponse 的 .Result 上产生此错误。错误 1“System.Threading.Tasks.Task”不包含“Result”的定义,并且找不到接受“System.Threading.Tasks.Task”类型的第一个参数的扩展方法“Result”(您是否缺少使用指令还是程序集引用?) 这没有任何意义,改变ReadAsAsync()中的类型并不能改变代码之前的行为方式。 不错。它嵌入在 ContinueWith 语句中。请创建一个新的 MVC4 应用程序(在 VS 2012 中工作)并粘贴到这个控制器和两个类中?你能复制错误吗?如果是这样,您能否建议一个经过测试的解决方案来解决问题? 【参考方案1】:

尝试使用 Json2csharp.com 网站之类的工具,而不是手动调整模型。粘贴在示例 JSON 响应中,越完整越好,然后拉入生成的生成类。至少,这会带走一些移动部件,让您在 csharp 中获得 JSON 的形状,让序列化器更轻松,并且您不必添加属性。

只要让它工作,然后修改你的类名,以符合你的命名约定,然后再添加属性。

编辑: 好的,经过一番折腾后,我已成功将结果反序列化为作业列表(我使用 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 属性,而在于如何使用 .NET 4.0 中的任务模式将返回的 JSON 反序列化为数组或列表。 是的,正如我从一开始就说的,这是一个 JSON 反序列化问题。捕获字符串,然后 JsonConverting 成功了,现在它可以工作了。不需要修改 Job 类型,我保留了我原来的 Job 类型和 JsonProperty 属性。 如果您只是要同步等待它们完成,那么使用异步方法获取数据是没有意义的。如果你只是要同步等待,你也可以从一开始就使用同步方法。如果要使用异步方法,代码实际上应该是异步的。 @Joe 在哪方面?他注意到可以修复的代码有些奇怪,使其更清晰。异步代码变得模糊不清,Servy 提出了一个很好的观点。也许评论更适合,但无论哪种方式,反对票不一定是永久性的。 第二次编辑让我很开心。谢谢!【参考方案2】:
var response = taskwithresponse.Result;
          var jsonString = response.ReadAsAsync<List<Job>>().Result;

【讨论】:

对于任何想知道扩展方法在哪里的人:Microsoft.AspNet.WebApi.Client nuget 包。 永远不要使用.Result,因为它会阻塞线程。应该使用类似:var jsonString = await taskwithresponse.ReadAsAsync&lt;List&lt;Job&gt;&gt;()【参考方案3】:

返回类型取决于服务器,有时响应确实是 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&lt;List&lt;Job&gt;&gt;();

以上是关于使用 .NET 4.0 任务模式使用 HTTPClient .ReadAsAsync 将 JSON 反序列化为数组或列表的主要内容,如果未能解决你的问题,请参考以下文章

WCF 与 3.5 和 4.0 一起使用

.NET中的异步

是不是 .NET 4.0 TPL 让 APM、EAP 和 BackgroundWorker 异步模式过时了?

.net 4.0 中的特性总结:垃圾回收

巡航控制 .NET 4.0 MSBUILD 记录器

使用 Powershell 4.0 更新计划任务操作参数