Rust:在将 json 解析为复杂枚举时如何最小化模式匹配

Rust: how to minimize patternmatching when parsing json into complex enum

所以,假设我期望从网络流中获得许多已知格式的不同 JSON。我为它们定义结构并用代表所有可能性的枚举包装它们:

use serde::Deserialize;
use serde_json;

#[derive(Deserialize, Debug)]
struct FirstPossibleResponse {
    first_field: String,
}

#[derive(Deserialize, Debug)]
struct SecondPossibleResponse {
    second_field: String,
}

#[derive(Deserialize, Debug)]
enum ResponseFromNetwork {
    FirstPossibleResponse(FirstPossibleResponse),
    SecondPossibleResponse(SecondPossibleResponse),
}

然后,作为一个 聪明 的人,我想为自己提供一种将这些 JSON 解析为我的结构的简短方法,所以我正在实现一个特征(这里是问题):

impl From<String> for ResponseFromNetwork {
    fn from(r: String) -> Self {
        match serde_json::from_slice(&r.as_bytes()) {
            Ok(v) => ResponseFromNetwork::FirstPossibleResponse(v),
            Err(_) => match serde_json::from_slice(&r.as_bytes()) {
                Ok(v) => ResponseFromNetwork::SecondPossibleResponse(v),
                Err(_) => unimplemented!("idk"),
            },
        }
    }
}

...以后像这样使用它:

fn main() {
    let data_first = r#"
        {
            "first_field": "first_value"
        }"#;
    let data_second = r#"
        {
            "second_field": "first_value"
        }"#;
        
    print!("{:?}", ResponseFromNetwork::from(data_first.to_owned()));
    print!("{:?}", ResponseFromNetwork::from(data_second.to_owned()));
}

Rust playground

所以,如前所述,问题是 - 这个 match 树是我进行解析工作的唯一途径,你可以想象 - 我可能通过网络获得的不同 JSON 的变体越多- 树长得越深越脏。

我想以某种方式拥有它,例如解析一次,然后根据值进行操作:

use serde_json::Result;

impl From<String> for ResponseFromNetwork {
    fn from(r: String) -> Self {
        let parsed: Result<ResponseFromNetwork> = serde_json::from_slice(r.as_bytes());
        match parsed {
            Ok(v) => {
                match v {print!("And here we should match on invariants or something: {:?}", v);
                v
            }
            Err(e) => unimplemented!("{:?}", e),
        }
    }
}

但它并没有真正起作用:

thread 'main' panicked at 'not implemented: Error("unknown variant `first_field`, expected `FirstPossibleResponse` or `SecondPossibleResponse`", line: 3, column: 25)', src/main.rs:28:23

Playground

#[serde(untagged)] 正是为该用例而设计的。只需将它添加到 enum ResponseFromNetwork 的定义前面,您的代码就会按照您希望的方式工作:

#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum ResponseFromNetwork {
    FirstPossibleResponse(FirstPossibleResponse),
    SecondPossibleResponse(SecondPossibleResponse),
}

impl From<String> for ResponseFromNetwork {
    fn from(r: String) -> Self {
        match serde_json::from_slice(r.as_bytes()) {
            Ok(v) => v,
            Err(e) => unimplemented!("{:?}", e),
        }
    }
}

Playground

如果响应 JSON 字符串的格式可以扩展(如果它们是预定义且不可更改的,则可能不会),在每个 JSON 中添加一个标记字段,例如“种类”,并且用 #[serde(tag = "kind")] 注释每个变体结构,用 #[serde(untagged)] 注释枚举可以解决这个问题。 [playground]

use serde::Deserialize;
use serde_json;

#[derive(Deserialize, Debug)]
#[serde(tag = "kind")]
struct FirstPossibleResponse {
    first_field: String,
}

#[derive(Deserialize, Debug)]
#[serde(tag = "kind")]
struct SecondPossibleResponse {
    second_field: String,
}

#[derive(Deserialize, Debug)]
#[serde(tag = "kind")]
struct ThirdPossibleResponse {
    third_field: String,
}

#[derive(Deserialize, Debug)]
#[serde(untagged)]
enum ResponseFromNetwork {
    FirstPossibleResponse(FirstPossibleResponse),
    SecondPossibleResponse(SecondPossibleResponse),
    ThirdPossibleResponse(ThirdPossibleResponse),
}

impl From<String> for ResponseFromNetwork {
    fn from(r: String) -> Self {
        match serde_json::from_slice(&r.as_bytes()) {
            Ok(v) => v,
            Err(_) => unimplemented!("idk"),
        }
    }
}

fn main() {
    let data_first = r#"
    {
        "kind":"FirstPossibleResponse",
        "first_field": "first_value"
    }"#;
    let data_second = r#"
    {
        "kind":"SecondPossibleResponse",
        "second_field": "second_value"
    }"#;
    let data_third = r#"
    {
        "kind":"ThirdPossibleResponse",
        "third_field": "third_value"
    }"#;
    println!("{:?}", ResponseFromNetwork::from(data_first.to_owned()));
    println!("{:?}", ResponseFromNetwork::from(data_second.to_owned()));
    println!("{:?}", ResponseFromNetwork::from(data_third.to_owned()));
}