3

I'm trying to deserialize some JSON: {"id":1,"status":"Failed","cause":"Error"} where the status can be one of "Executing", "Successful" or "Failed" my issue is that I only get a "cause" if the status is "Failed".

I don't want the cause to be an Option<String> because I always get a cause if the status is "Failed" and I never get a cause if the status is "Executing" or "Successful". I would rather have somthing like:

use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct Download {
    id: i32,
    status: Status,
}

#[derive(Deserialize, Debug)]
#[serde(tag = "status")]
enum Status {
    Executing,
    Successful,
    Failed {
        cause: String
    },
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn status_from_executing() {
        let s = r#"{"status":"Executing"}"#;
        serde_json::from_str::<Status>(s).unwrap();
    }

    #[test]
    fn status_from_succeeded() {
        let s = r#"{"status":"Successful"}"#;
        serde_json::from_str::<Status>(s).unwrap();
    }

    #[test]
    fn status_from_failed() {
        let s = r#"{"status":"Failed","cause":"Bad thing"}"#;
        serde_json::from_str::<Status>(s).unwrap();
    }

    #[test]
    fn download_from_executing() {
        let s = r#"{"id":1,"status":"Executing"}"#;
        serde_json::from_str::<Download>(s).unwrap();
    }

    #[test]
    fn download_from_succeeded() {
        let s = r#"{"id":1,"status":"Successful"}"#;
        serde_json::from_str::<Download>(s).unwrap();
    }

    #[test]
    fn download_from_failed() {
        let s = r#"{"id":1,"status":"Failed","cause":"Bad thing"}"#;
        serde_json::from_str::<Download>(s).unwrap();
    }
}

In the snippit above, all of the status_from tests pass and the download_from tests fail. If I remove the #[serde(tag = "status")] attribute from status then only download_from_executing and download_from_succeeded pass.

I only care about being able to deserialize the download struct.

I would like to find the right set of serde attributes to deserialize the download struct when I have a JSON body that has status "Failed" and a cause.

My Cargo.toml:

[package]
name = "example"
version = "0.1.0"
edition = "2018"

[dependencies]
serde = { version = "1.0.130", features = ["derive"] }
serde_json = "1.0.68"
  • you could write your own deserializer, then deserialize into a flatten struct, then just nested into your final struct. – Netwave Sep 26 '21 at 16:47

1 Answers1

2

You can use the #[serde(flatten)] attribute on Download:

#[derive(Deserialize, Debug)]
struct Download {
    id: i32,
    #[serde(flatten)]
    status: Status,
}

This makes all of your tests pass: playground.

apilat
  • 1,370
  • 8
  • 17
  • ahhh thank you, I had misread the docs and thought that flatten could only be applied onto structs but its says within stucts. – Jack Johnstone Sep 26 '21 at 17:12