首页 > 解决方案 > 从 serde_json 中的无类型 JSON 中提取数据时如何处理错误?

问题描述

我有一个serde_json::Value我希望包含一个对象数组的对象。从这些对象中,我想提取 2 个值并在任何失败时返回错误。到目前为止,这是我的代码:

use std::collections::HashMap;
use anyhow::Result;


fn get_stock(response: serde_json::Value) -> Result<HashMap<String, u32>>{
    response["products"]
        .as_array()?.iter()
        .map(|product| {
            let product = product.as_object()?;
            (
                product["name"].as_str()?.to_owned(),
                //as_u64 fails for some reason
                product["stock"].as_str()?.parse::<u32>()?,
            )
        })
        .collect()?
}

当我使用.unwrap()它时效果很好,但是在将返回类型更改为Result并将 unwraps 替换为之后,?我得到了以下编译错误:

error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result`
  --> src/bin/products.rs:7:20
   |
5  | / fn get_stock(response: serde_json::Value) -> Result<HashMap<String, u32>>{
6  | |     response["products"]
7  | |         .as_array()?.iter()
   | |                    ^ use `.ok_or(...)?` to provide an error compatible with `Result<HashMap<std::string::String, u32>, anyhow::Error>`
8  | |         .map(|product| {
...  |
16 | |         .collect()?
17 | | }
   | |_- this function returns a `Result`
   |
   = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `Result<HashMap<std::string::String, u32>, anyhow::Error>`
   = note: required by `from_residual`

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/bin/products.rs:9:46
   |
8  |           .map(|product| {
   |  ______________-
9  | |             let product = product.as_object()?;
   | |                                              ^ cannot use the `?` operator in a closure that returns `(std::string::String, u32)`
10 | |             (
11 | |                 product["name"].as_str()?.to_owned(),
...  |
14 | |             )
15 | |         })
   | |_________- this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `(std::string::String, u32)`
   = note: required by `from_residual`

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/bin/products.rs:11:41
   |
8  |           .map(|product| {
   |  ______________-
9  | |             let product = product.as_object()?;
10 | |             (
11 | |                 product["name"].as_str()?.to_owned(),
   | |                                         ^ cannot use the `?` operator in a closure that returns `(std::string::String, u32)`
...  |
14 | |             )
15 | |         })
   | |_________- this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `(std::string::String, u32)`
   = note: required by `from_residual`

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/bin/products.rs:13:42
   |
8  |           .map(|product| {
   |  ______________-
9  | |             let product = product.as_object()?;
10 | |             (
11 | |                 product["name"].as_str()?.to_owned(),
12 | |                 //as_u64 fails for some reason
13 | |                 product["stock"].as_str()?.parse::<u32>()?,
   | |                                          ^ cannot use the `?` operator in a closure that returns `(std::string::String, u32)`
14 | |             )
15 | |         })
   | |_________- this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `(std::string::String, u32)`
   = note: required by `from_residual`

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/bin/products.rs:13:58
   |
8  |           .map(|product| {
   |  ______________-
9  | |             let product = product.as_object()?;
10 | |             (
11 | |                 product["name"].as_str()?.to_owned(),
12 | |                 //as_u64 fails for some reason
13 | |                 product["stock"].as_str()?.parse::<u32>()?,
   | |                                                          ^ cannot use the `?` operator in a closure that returns `(std::string::String, u32)`
miav@battlestation catbot % cargo run --bin products
   Compiling catbot v0.1.0 (/Users/miav/Documents/Personal/Projects/programming/catbot)
error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result`
  --> src/bin/products.rs:7:20
   |
5  | / fn get_stock(response: serde_json::Value) -> Result<HashMap<String, u32>>{
6  | |     response["products"]
7  | |         .as_array()?.iter()
   | |                    ^ use `.ok_or(...)?` to provide an error compatible with `Result<HashMap<std::string::String, u32>, anyhow::Error>`
8  | |         .map(|product| {
...  |
16 | |         .collect()?
17 | | }
   | |_- this function returns a `Result`
   |
   = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `Result<HashMap<std::string::String, u32>, anyhow::Error>`
   = note: required by `from_residual`

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/bin/products.rs:9:46
   |
8  |           .map(|product| {
   |  ______________-
9  | |             let product = product.as_object()?;
   | |                                              ^ cannot use the `?` operator in a closure that returns `(std::string::String, u32)`
10 | |             (
11 | |                 product["name"].as_str()?.to_owned(),
...  |
14 | |             )
15 | |         })
   | |_________- this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `(std::string::String, u32)`
   = note: required by `from_residual`

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/bin/products.rs:11:41
   |
8  |           .map(|product| {
   |  ______________-
9  | |             let product = product.as_object()?;
10 | |             (
11 | |                 product["name"].as_str()?.to_owned(),
   | |                                         ^ cannot use the `?` operator in a closure that returns `(std::string::String, u32)`
...  |
14 | |             )
15 | |         })
   | |_________- this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `(std::string::String, u32)`
   = note: required by `from_residual`

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/bin/products.rs:13:42
   |
8  |           .map(|product| {
   |  ______________-
9  | |             let product = product.as_object()?;
10 | |             (
11 | |                 product["name"].as_str()?.to_owned(),
12 | |                 //as_u64 fails for some reason
13 | |                 product["stock"].as_str()?.parse::<u32>()?,
   | |                                          ^ cannot use the `?` operator in a closure that returns `(std::string::String, u32)`
14 | |             )
15 | |         })
   | |_________- this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `(std::string::String, u32)`
   = note: required by `from_residual`

error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
  --> src/bin/products.rs:13:58
   |
8  |           .map(|product| {
   |  ______________-
9  | |             let product = product.as_object()?;
10 | |             (
11 | |                 product["name"].as_str()?.to_owned(),
12 | |                 //as_u64 fails for some reason
13 | |                 product["stock"].as_str()?.parse::<u32>()?,
   | |                                                          ^ cannot use the `?` operator in a closure that returns `(std::string::String, u32)`
14 | |             )
15 | |         })
   | |_________- this function should return `Result` or `Option` to accept `?`
   |
   = help: the trait `FromResidual<Result<Infallible, ParseIntError>>` is not implemented for `(std::string::String, u32)`
   = note: required by `from_residual`

error: aborting due to 5 previous errors

我事先不知道 JSON 的确切结构,因此无法将其解析为结构。我所知道的是,它很可能是一个包含name字符串字段和stock整数字段的对象数组,我想将它们提取到地图中。如果情况并非如此,那么我想返回一个错误。最简单的方法是什么?

更新:在遵循 Lagerbaer 的建议并进行一些挖掘后,我想出了以下解决方案。

use anyhow::{anyhow, Result};
use std::collections::HashMap;

fn get_stock(response: serde_json::Value) -> Result<HashMap<String, u32>> {
    response
        .get("products")
        .ok_or(anyhow!("'products' not found"))?
        .as_array()
        .ok_or(anyhow!("'products' is not an array"))?
        .iter()
        .map(|product| -> Result<(String, u32)> {
            let product = product.as_object().unwrap();
            Ok((
                product
                    .get("name")
                    .ok_or(anyhow!("'name' not found"))?
                    .as_str()
                    .ok_or(anyhow!("'name' is not a string"))?
                    .trim()
                    .to_owned(),
                //as_u64 fails for some reason
                product
                    .get("stock")
                    .ok_or(anyhow!("'stock' not found"))?
                    .as_str()
                    .ok_or(anyhow!("'stock' is not a string"))?
                    .parse::<u32>()?,
            ))
        })
        .collect()
}

用于ok_or()映射OptionResult,必须使用anyhow!宏使其与无论如何结果类型兼容。事实还证明,它collect()实际上接受了一个迭代器 Result<(String, u32)>来生成Result<HashMap<String, u32>>具有我想要的确切行为的 a,也就是说,如果没有错误,则返回第一个错误或完整的哈希映射。

标签: jsonrustserdeserde-json

解决方案


所以这里有几个问题,编译器会尽力告诉你。

我会让你从第一个开始,然后我鼓励你尝试单独尝试一下。

因此,第一个错误显然是as_array返回 anOption而不是Result,因此,您不能在?那里使用运算符。

幸运的是,编译器会告诉你该做什么:Option有一个ok_or你应该调用的方法。它会Option变成Result. 如果Option是,Some(value)你会得到一个Ok(value),如果OptionNone,你会得到你在ok_or参数中指定的任何错误。

另一个容易发现的错误是:您的函数返回 a Result,因此返回值显然应该是 a Result。因此,最后的最后一个?应该被删除,因为这?需要 aResult并将其转换为“纯”值(如果结果是Ok)。

然后剩下的就是弄清楚闭包应该返回什么。也许您可以在修复我上面解释的其他错误后尝试。


推荐阅读