反序列化 reqwest 提供的这种嵌套 JSON 结构

Deserialising this kind of nested JSON structure delivered by reqwest

提问人:mike rodent 提问时间:10/5/2023 最后编辑:mike rodent 更新时间:10/6/2023 访问量:62

问:

有人可能会指出这一点的重复。但我搜索过。关于这个主题有很多问题,但我似乎没有一个真正有帮助。

我交付的 JSON(来自 Elasticsearch)的结构是这样的:

当我去的时候:

let json_hashmap: HashMap<String, Value> = serde_json::from_str(&text).unwrap();

我明白了:

{
    "hits": Object {
        "hits": Array [
            Object {
                "_id": String("Ybgt6ooBpXznUptX4lR2"),
                "_index": String("booby"),
                "_score": Number(1.0),   
                ... (other keys)
            ],
            ... (multiple other hit-hit Objects in the Array)
         ],
         ... (other keys)
    }
    "key2": String("my arbitrary string value"),
    "key3": Number(1.0),
    ... (other keys)
}

这并不是说命中“字典”的键和嵌套数组是不可预测的。我可以为这些热门词典设计一个。但是,我怎样才能真正获得命中数组呢?struct

因为可以看出,0 级字典(外部字典)有一个键,该键解码为包含内部-内部字典数组(“级别 2”)的内部字典(“级别 1”)。

但是 0 级字典有除“命中”之外的键,这些键可以解码为其他值。事实上,1 级词典也可能有“命中”以外的键。

有没有办法规定“级别 0”(外部)字典中的一个特定键解码为,而该字典中的一个特定键解码为?HashMap<String, Value>Vec<MyStruct>

注意:如果我去,我可以访问一些对象

let outer_hits = &json_hasmap["hits"];
let inner_hits = &outer_hits["hits"];

打印此文件显示:{:#?}

Array [
    Object {
        "_id": String("Ybgt6ooBpXznUptX4lR2"),
        "_index": String("booby"),
        "_score": Number(1.0),
        "_source": Object {
            "docx_id": Number(14),
            "last_modif_ms": Number(1596958203000),
            "ldoc_type": String("docx_doc"),
            "path": String("D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-03.docx"),
        },
    },
    Object {
        ...

...但是,例如,虽然这被称为类型,但我的尝试不会编译:我得到“ ^^^ 方法未找到 ”。奇怪的“数组”。我怎样才能提取更有用的东西?Arrayinner_hits.len()&Value

text一开始是:

{
  "_scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDnF1ZXJ5VGhlbkZldGNoBxZQcGUxbjhnQ1RzU0M1bG1ZR05od1VBAAAAAAAAASAWbkU3ck5YUTNSazZlazJtUXctSklWQRZQcGUxbjhnQ1RzU0M1bG1ZR05od1VBAAAAAAAAASEWbkU3ck5YUTNSazZlazJtUXctSklWQRZQcGUxbjhnQ1RzU0M1bG1ZR05od1VBAAAAAAAAASIWbkU3ck5YUTNSazZlazJtUXctSklWQRZQcGUxbjhnQ1RzU0M1bG1ZR05od1VBAAAAAAAAASMWbkU3ck5YUTNSazZlazJtUXctSklWQRZQcGUxbjhnQ1RzU0M1bG1ZR05od1VBAAAAAAAAASQWbkU3ck5YUTNSazZlazJtUXctSklWQRZQcGUxbjhnQ1RzU0M1bG1ZR05od1VBAAAAAAAAASUWbkU3ck5YUTNSazZlazJtUXctSklWQRZQcGUxbjhnQ1RzU0M1bG1ZR05od1VBAAAAAAAAASYWbkU3ck5YUTNSazZlazJtUXctSklWQQ==",
  "took": 3,
  "timed_out": false,
  "_shards": {
    "total": 10,
    "successful": 10,
    "skipped": 3,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 5303,
      "relation": "eq"
    },
    "max_score": 1,
    "hits": [
      {
        "_index": "booby",
        "_id": "Ybgt6ooBpXznUptX4lR2",
        "_score": 1,
        "_source": {
          "docx_id": 14,
          "last_modif_ms": 1596958203000,
          "ldoc_type": "docx_doc",
          "path": "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-03.docx"
        }
      },
      {
        "_index": "booby",
        "_id": "jLgt6ooBpXznUptX4lSG",
        "_score": 1,
        "_source": {
          "docx_id": 12,
          "last_modif_ms": 1596958248000,
          "ldoc_type": "docx_doc",
          "path": "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-01.docx"
        }
      },
      {
        "_index": "booby",
        "_id": "1bgt6ooBpXznUptX4lSX",
        "_score": 1,
        "_source": {
          "docx_id": 20,
          "last_modif_ms": 1596958053000,
          "ldoc_type": "docx_doc",
          "path": "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-09.docx"
        }
      },
      {
        "_index": "booby",
        "_id": "7rgt6ooBpXznUptX4lSm",
        "_score": 1,
        "_source": {
          "docx_id": 21,
          "last_modif_ms": 1596958032000,
          "ldoc_type": "docx_doc",
          "path": "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-10.docx"
        }
      },
      {
        "_index": "booby",
        "_id": "_rgt6ooBpXznUptX4lS2",
        "_score": 1,
        "_source": {
          "docx_id": 22,
          "last_modif_ms": 1596957990000,
          "ldoc_type": "docx_doc",
          "path": "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-11.docx"
        }
      }
    ]
  }
}
Rust 反序列化 serde-json

评论

0赞 Chayim Friedman 10/5/2023
如果显示实际的 JSON,而不是调试表示,那就更好了。serde_json::Value
0赞 mike rodent 10/5/2023
谢谢,但我能澄清一下你在那里要求什么吗?因为我一开始就使用了,因为内容太复杂而引发了错误。你是说最后的那位吗?textjson
0赞 Chayim Friedman 10/5/2023
我的意思是实际的JSON文本,在解析它之前。它只是更容易理解。
0赞 mike rodent 10/5/2023
好的,已经做到了......但我不确定如何在 Rust 中漂亮地打印一个 json 字符串!查查...
0赞 Chayim Friedman 10/5/2023
请删除反斜杠。网上有很多JSON格式化程序。

答:

1赞 Chayim Friedman 10/5/2023 #1

要从 serde_json::Value 中提取可行的类型(等),您可以对其进行模式匹配或使用辅助方法(例如)。VecHashMapas_X()as_array().unwrap().len()

但是,使用 serde 总是最好使用类型化反序列化而不是泛型 .访问更容易,反序列化更快。Value

评论

0赞 mike rodent 10/5/2023
谢谢,很有帮助。事实上,我想使用类型化反序列化,但正如你所看到的,在我的例子中,我有一个字典,其中一个键是一个内部字典,其中一个键是字典的 Vec(或数组),这些字典是可以表征的结构。难题是如何将这种结构应用于内部词典的数组。我正在寻找一个可以执行“as_struct(MyStruct)”的函数。as_X
0赞 mike rodent 10/5/2023
...但是对于该数组中的每个元素,都提供了完全可行的东西。as_object
0赞 mike rodent 10/5/2023
至于“模式匹配”来获取类型,搜索后我想知道您的意思是......?serde_json.from_value()
0赞 Chayim Friedman 10/5/2023
@mikerodent 不,我的意思是.match x { serde_json::Value::Array(x) => if let serde_json::Value::Array(x) = x
0赞 kmdreko 10/5/2023
@mikerodent 我没有看到类型化反序列化的混淆,不要将它们视为字典,将它们视为具有不同类型字段的对象并像这样对其进行反序列化:playground。或者,也许您错过了serde系统的全部要点?
1赞 kmdreko 10/5/2023 #2

最好对自己的类型进行建模,以反映预期的 JSON 结构,而不是使用泛型 .您可以通过实现 serde::D eserialize 来实现这一点。像这样的东西(只实现了一些字段):Value

use serde::Deserialize;
use serde_json::json;

#[derive(Debug, Deserialize)]
struct Response {
    // _scroll_id
    // took
    // timed_out
    // _shards
    hits: HitsResponse,
}

#[derive(Debug, Deserialize)]
struct HitsResponse {
    // total
    // max_score
    hits: Vec<Hit>,
}

#[derive(Debug, Deserialize)]
struct Hit {
    // _index
    _id: String,
    // _score
    _source: HitSource,
}

#[derive(Debug, Deserialize)]
struct HitSource {
    // docx_id
    // last_modif_ms,
    // ldoc_type
    path: String,
}

将 JSON 解析为上述内容将产生以下结果:Response

let response: Response = serde_json::from_str(&text).unwrap();
Response {
    hits: HitsResponse {
        hits: [
            Hit {
                _id: "Ybgt6ooBpXznUptX4lR2",
                _source: HitSource {
                    path: "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-03.docx",
                },
            },
            Hit {
                _id: "jLgt6ooBpXznUptX4lSG",
                _source: HitSource {
                    path: "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-01.docx",
                },
            },
            Hit {
                _id: "1bgt6ooBpXznUptX4lSX",
                _source: HitSource {
                    path: "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-09.docx",
                },
            },
            Hit {
                _id: "7rgt6ooBpXznUptX4lSm",
                _source: HitSource {
                    path: "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-10.docx",
                },
            },
            Hit {
                _id: "_rgt6ooBpXznUptX4lS2",
                _source: HitSource {
                    path: "D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-11.docx",
                },
            },
        ],
    },
}

游乐场链接

如果您使用的是 reqwest,您可以直接使用 .json 而不是 + (通过启用其功能)。.text.bytesserde_json"json"

评论

0赞 mike rodent 10/5/2023
谢谢。与此同时,我偶然发现了......但这看起来是最好的解决方案。我没有意识到这些可以排除某些字段/键,这似乎是这里发生的事情。serde_json::from_valuestruct
0赞 mike rodent 10/5/2023 #3

作为一个几乎没有尿布的 Rust 新手(可能很明显),考虑到对这个问题感兴趣的 2 个人的知识,我犹豫是否要提供这个解决方案。但这似乎有效。我不知道这是否是一个好的解决方案。

#[derive(Debug, Serialize, Deserialize)]
struct HitSource {
    docx_id: usize,
    last_modif_ms: usize,
    ldoc_type: String,
    path: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct Hit {
    _id: String,
    _index: String,
    _score: f64,
    _source: HitSource,
}

...
let hits_dict = &json_hashmap["hits"];
let hits_array = &hits_dict["hits"].as_array().unwrap();
...

for hit in *hits_array {
   /*
NB each hit looks like this:   
{
    "_id": String("1bgt6ooBpXznUptX4lSX"),
    "_index": String("booby"),
    "_score": Number(1.0),
    "_source": Object {
        "docx_id": Number(20),
        "last_modif_ms": Number(1596958053000),
        "ldoc_type": String("docx_doc"),
        "path": String("D:\\My Documents\\doc\\IT_Diary\\IT Diary $UPD 2018-09.docx"),
    },
}      
   */
   
   let hit_result: Result<Hit, _> = serde_json::from_value(hit.clone());
   info!("{:#?}", hit_result);
}

...这确实提供了 非错误 ,这似乎没问题。Result<Hit, _>

然而,kmdreko 的解决方案似乎是规范的解决方案:他的解决方案的关键是,在任何这些字典/地图中找到的任何键都不能被解释为所需的结构,都会被忽略:在所示的情况下,诸如“_scroll_id”、“taken”、“timed_out”、“_shards”等键。struct

我发现,如果我试图取消(他的外部字典/地图)并改用,这会引起一个错误......因为其他键,其值不能解释为 .如果所有人都可以,似乎可以工作。struct ResponsestructHashMap<String, HitsResponse>HitsResponse