kek_mek
kek_mek

Reputation: 125

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

So, let's say I am expecting a lot of different JSONs of a known format from a network stream. I define structures for them and wrap them with an enum representing all the possibilities:

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),
}

Then, being a smart folk, I want to provide myself with a short way of parsing these JSONs into my structures, so I am implementing a trait (and here is the problem):

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"),
            },
        }
    }
}

...To use it later like this:

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

So, as mentioned earlier, the problem is that - this match tree is the only way I got parsing work, and you can imagine - the more variations of different JSONs I might possibly get over the network - the deeper and nastier the tree grows.

I want to have it in some way like ths, e.g. parse it once and then operate depending on the value:

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),
        }
    }
}

But it doesn't really work:

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

Upvotes: 2

Views: 355

Answers (2)

user4815162342
user4815162342

Reputation: 155186

#[serde(untagged)] is designed for precisely that use case. Just add it in front of the definition of enum ResponseFromNetwork and your code will work the way you want it to:

#[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

Upvotes: 2

Joe_Jingyu
Joe_Jingyu

Reputation: 1279

If the formats of the response JSON strings can be extended (maybe not if they are predefined and unchangable), adding a tag field, say "kind", in each JSON, and annotating each variant struct with #[serde(tag = "kind")] and the enum with #[serde(untagged)] can address the issue. [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()));
}

Upvotes: 1

Related Questions