使用Serde反序列化嵌套的JSON结构时出现“无效类型:映射,期望序列”。

23

我正在尝试轮询GitHub API以获取问题并将它们打印出来。为此,我需要反序列化从cURL GET请求接收到的嵌套JSON结构。

我试图获取items数组中所有对象的url

{
 "total_count": 4905,
 "incomplete_results": false,
 "items": [
    {
     "url": "https://api.github.com/repos/servo/saltfs/issues/789",
     "repository_url": "https://api.github.com/repos/servo/saltfs",
     "labels_url": 
   "https://api.github.com/repos/servo/saltfs/issues/789/labels{/name}",
  "comments_url": "https://api.github.com/repos/servo/saltfs/issues/789/comments",
  "events_url": "https://api.github.com/repos/servo/saltfs/issues/789/events",
  "html_url": "https://github.com/servo/saltfs/issues/789",
  "id": 293260512,
  "number": 789,
  "title": "Stop setting $CARGO_HOME to its default value",
  "user": {
    "login": "SimonSapin",
    "id": 291359,
    "avatar_url": "https://avatars0.githubusercontent.com/u/291359?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/SimonSapin",
    "html_url": "https://github.com/SimonSapin",
    "followers_url": "https://api.github.com/users/SimonSapin/followers",
    "following_url": "https://api.github.com/users/SimonSapin/following{/other_user}",
    "gists_url": "https://api.github.com/users/SimonSapin/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/SimonSapin/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/SimonSapin/subscriptions",
    "organizations_url": "https://api.github.com/users/SimonSapin/orgs",
    "repos_url": "https://api.github.com/users/SimonSapin/repos",
    "events_url": "https://api.github.com/users/SimonSapin/events{/privacy}",
    "received_events_url": "https://api.github.com/users/SimonSapin/received_events",
    "type": "User",
    "site_admin": false
  },
  "labels": [
    {
      "id": 341722396,
      "url": "https://api.github.com/repos/servo/saltfs/labels/E-easy",
      "name": "E-easy",
      "color": "02e10c",
      "default": false
    }
  ],
  "state": "open",
  "locked": false,
  "assignee": null,
  "assignees": [

  ],
  "milestone": null,
  "comments": 0,
  "created_at": "2018-01-31T18:16:09Z",
  "updated_at": "2018-01-31T18:16:49Z",
  "closed_at": null,
  "author_association": "MEMBER",
  "body": "In `buildbot/master/files/config/environments.py` we set `CARGO_HOME` to Cargo’s default value. Now that `mach` does not set it (since https://github.com/servo/servo/pull/19395), this has no effect. We can remove these lines.",
  "score": 1.0
},
{
  "url": "https://api.github.com/repos/servo/servo/issues/19916",
  "repository_url": "https://api.github.com/repos/servo/servo",
  "labels_url": "https://api.github.com/repos/servo/servo/issues/19916/labels{/name}",
  "comments_url": "https://api.github.com/repos/servo/servo/issues/19916/comments",
  "events_url": "https://api.github.com/repos/servo/servo/issues/19916/events",
  "html_url": "https://github.com/servo/servo/issues/19916",
  "id": 293237180,
  "number": 19916,
  "title": "Use a macro to create null-terminated C strings",
  "user": {
    "login": "jdm",
    "id": 27658,
    "avatar_url": "https://avatars1.githubusercontent.com/u/27658?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/jdm",
    "html_url": "https://github.com/jdm",
    "followers_url": "https://api.github.com/users/jdm/followers",
    "following_url": "https://api.github.com/users/jdm/following{/other_user}",
    "gists_url": "https://api.github.com/users/jdm/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/jdm/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/jdm/subscriptions",
    "organizations_url": "https://api.github.com/users/jdm/orgs",
    "repos_url": "https://api.github.com/users/jdm/repos",
    "events_url": "https://api.github.com/users/jdm/events{/privacy}",
    "received_events_url": "https://api.github.com/users/jdm/received_events",
    "type": "User",
    "site_admin": false
  },
  "labels": [
    {
      "id": 89384911,
      "url": "https://api.github.com/repos/servo/servo/labels/C-assigned",
      "name": "C-assigned",
      "color": "02d7e1",
      "default": false
    },
    {
      "id": 15997664,
      "url": "https://api.github.com/repos/servo/servo/labels/E-easy",
      "name": "E-easy",
      "color": "02e10c",
      "default": false
    },
    {
      "id": 135307111,
      "url": "https://api.github.com/repos/servo/servo/labels/I-cleanup",
      "name": "I-cleanup",
      "color": "e11d21",
      "default": false
    }
  ],
  "state": "open",
  "locked": false,
  "assignee": null,
  "assignees": [

  ],
  "milestone": null,
  "comments": 3,
  "created_at": "2018-01-31T17:04:06Z",
  "updated_at": "2018-01-31T22:03:56Z",
  "closed_at": null,
  "author_association": "MEMBER",
  "body": "When we write them by hand (eg. `b\"some string\\0\"`), we invariably get them wrong in ways that are tricky to notice (https://github.com/servo/servo/pull/19915). We should use a macro like this instead:\r\n```rust\r\nmacro_rules! c_str {\r\n    ($str:expr) => {\r\n        concat!($str, \"\\0\").as_bytes()\r\n    }\r\n}\r\n```\r\nThis would allow us to write code like `(c_str!(\"PEParseDeclarationDeclExpected\"), Action::Skip)` instead of https://github.com/emilio/servo/blob/d82c54bd3033cc3277ebeb4854739bebe4e20f2f/ports/geckolib/error_reporter.rs#L237. We should be able to clean up all of the uses in that file.\r\n\r\nNo need to run any automated tests; if it builds with `./mach build-geckolib`, then it's good enough for a pull request.",
  "score": 1.0
}
]}

我的request函数发起cURL请求并收到上述JSON。然后我使用serde_json反序列化JSON。

main.rs

extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;

mod engine;
mod server;
use engine::request;
use std::string::String;
use self::serde_json::{Error, Value};

#[derive(Serialize, Deserialize)]
struct obj {
    items: Vec<String>,
}

fn main() {
    let output_jn: String = request(
        "https://api.github.com/search/issues?q=is:issue+label:e-easy",
    ).to_string(); //gets json structure as string
    let json: obj = serde_json::from_str(&output_jn).unwrap();

    for elem in json.iter() {
        println!("{:?}", elem);
    }
}

我看到了以下错误信息。

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: 
ErrorImpl { code: Message("invalid type: map, expected a sequence"), 
line: 1, column: 0 }', libcore/result.rs:945:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

我肯定在反序列化我的JSON结构时犯了一个愚蠢的错误,我尝试了许多排列组合但是什么都没能做到。


“为什么你要将那个 JSON 对象反序列化成一个向量?你为什么从未使用过 obj(应该是 Obj)?” — let json: Vec<String> = serde_json::from_str - Shepmaster
那是一个打字错误,我试着尝试了一些在网上看到的东西,我正在使用结构体 obj 并得到这个错误。已经编辑了上面的代码。 - zep
1
好的,那么问题就变成了:为什么你要将那个JSON对象数组反序列化为字符串数组 - Shepmaster
我猜我的困惑是反序列化的正确数据结构应该是什么,应该是 items: Vec<Vec<String>> 吗? - zep
1个回答

32

看一下你的JSON输入数据中的这部分内容:

{
  ...
  "items": [
    {
      ...
      "title": "Stop setting $CARGO_HOME to its default value",
      ...
    }
  ]
}
  • 最高级别的数据结构是一个JSON映射,因此在Rust中,它将表示为一个结构体。我将使用您的名称 Obj
  • JSON映射的顶层键名为"items",因此在Rust中,这将成为Obj结构体内部的items字段。
  • 映射中"items"的值是一个JSON数组,因此在Rust中,我们可以使用Vec。
  • JSON数组中的每个元素都是一个JSON映射,因此在Rust中,我们需要一个用于它们的结构体。我们可以称其为Issue
  • 每个问题都有一个名为"title"的JSON键,因此这将是Issue结构体内的title字段。
  • "title"的值是一个JSON字符串,因此我们可以在字段中使用Rust的String类型。
#[derive(Deserialize, Debug)]
struct Obj {
    items: Vec<Issue>,
}

#[derive(Deserialize, Debug)]
struct Issue {
    title: String,
}

fn main() {
    let j = /* get the JSON data */;

    let issues = serde_json::from_str::<Obj>(j).unwrap();
    for i in issues.items {
        println!("{:#?}", i);
    }
}

1
顶层数据结构是一个JSON映射,因此在Rust中,它将表示为一个结构体。但这不一定需要是一个结构体 - 可以使用例如HashMap来完成。 - til
2
我的实验并不支持这一点,如果在这里的顶层(外部)字典/映射中除了“items”之外还有其他键,即具有与struct Issue不同的值的键,这在OP给出的示例中是存在的:“total_count”:某种整数,和“incomplete_results”:bool。然后我会得到一个错误。而如果一切都由一个struct覆盖,其他键就会被忽略。 - undefined

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