jsonrustenumsserde

Deserialize enum variant with and without fields


I have an Enum like the one below

#[serde(rename_all="snake_case")]
pub enum Flag {
    One,
    Two,
    Three { value: Option<Decimal>}
}

I want to match the following json representations to it

{"flag":"one"} // Working fine
{"flag":"two"} // Working fine
{"flag":{"three":{"value":###}} // Working fine -> Three { value: Some(###) }
{"flag":"three"} // not working, desired result -> Three { value: None }

How can I get such behavior?

I already tried:

Please help


Solution

  • Assuming you want the Flag as is, without any changes to the enum and its variants. Then it becomes a bit tricky, due to the structure of your JSON.

    When I encounter these simple vs complex forms of JSON data, then I usually implement it as multiple types. With an overall serde(untagged) enum.

    Note: That the order of the variants matter in serde(untagged), as serde will try the variants sequentially, and stop when one successfully deserializes.

    To keep your Flag as is, then you can use serde(from = ...) or serde(try_from ...) to deserialize through the other type. The other types can be kept private as well.

    For demonstration purposes, I've changed your Decimal to u32. But as long as Decimal implements Deserialize, then they are interchangeable.

    It would look something like this:

    use serde::Deserialize;
    
    #[derive(Deserialize, Debug)]
    #[serde(from = "ComplexFlag")]
    pub enum Flag {
        One,
        Two,
        Three { value: Option<u32> },
    }
    
    #[derive(Deserialize, Debug)]
    #[serde(untagged)]
    enum ComplexFlag {
        Three(ThreeFlag),
        Simple(SimpleFlag),
    }
    
    #[derive(Deserialize, Debug)]
    #[serde(rename_all = "snake_case")]
    enum SimpleFlag {
        One,
        Two,
        Three,
    }
    
    #[derive(Deserialize, Debug)]
    struct ThreeFlag {
        three: ThreeData,
    }
    
    #[derive(Deserialize, Debug)]
    struct ThreeData {
        value: Option<u32>,
    }
    

    Then all we need is the From<ComplexFlag> for Flag:

    impl From<ComplexFlag> for Flag {
        fn from(flag: ComplexFlag) -> Self {
            match flag {
                ComplexFlag::Simple(flag) => match flag {
                    SimpleFlag::One => Self::One,
                    SimpleFlag::Two => Self::Two,
                    SimpleFlag::Three => Self::Three { value: None },
                },
                ComplexFlag::Three(flag) => Self::Three {
                    value: flag.three.value,
                },
            }
        }
    }
    

    Testing it with the following:

    #[derive(Deserialize, Debug)]
    struct Data {
        flag: Flag,
    }
    
    let json = [
        r#"{"flag":"one"}"#,
        r#"{"flag":"two"}"#,
        r#"{"flag":"three"}"#,
        r#"{"flag":{"three":{"value":123}}}"#,
    ];
    
    for json in json {
        let data = serde_json::from_str::<Data>(json);
        println!("{:?}", data);
    }
    

    Then correctly yields:

    Ok(Data { flag: One })
    Ok(Data { flag: Two })
    Ok(Data { flag: Three { value: None } })     
    Ok(Data { flag: Three { value: Some(123) } })
    

    Alternatively, you could also manually implement Deserialize for Flag:

    impl<'de> Deserialize<'de> for Flag {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: Deserializer<'de>,
        {
            let value = Value::deserialize(deserializer)?;
    
            Ok(match value.as_str() {
                Some(flag) => match flag {
                    "one" => Self::One,
                    "two" => Self::One,
                    "three" => Self::Three { value: None },
                    flag => {
                        return Err(D::Error::custom(format!("unexpected flag {flag:?}")));
                    }
                },
                None => {
                    let three = ThreeFlag::deserialize(value).map_err(D::Error::custom)?;
                    Self::Three {
                        value: three.three.value,
                    }
                }
            })
        }
    }
    

    But that might seem a bit more hacky and harder to maintain. Compared to the duplicate code in SimpleFlag, and the manual From<ComplexFlag> for Flag implementation.