Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default variant for an internally tagged enum #1221

Closed
dtolnay opened this issue Apr 19, 2018 · 3 comments
Closed

Default variant for an internally tagged enum #1221

dtolnay opened this issue Apr 19, 2018 · 3 comments
Labels

Comments

@dtolnay
Copy link
Member

dtolnay commented Apr 19, 2018

Question from IRC:

<azdle> Is there anyway to set set a default variant for an internally tagged structure, that is to say on an enum with #[derive(tag = "foo")] can I set a variant to use when that field is undefined in some JSON I'm deserializing?

@dtolnay
Copy link
Member Author

dtolnay commented Apr 19, 2018

#[macro_use]
extern crate serde_derive;

extern crate serde;
extern crate serde_json;

use serde::de::{self, Deserialize, Deserializer};
use serde_json::Value;

#[derive(Debug)]
enum ConnectRequest {
    Unspecified(UnspecifiedSecurity),
    Wpa2(Wpa2Security),
}

#[derive(Deserialize, Debug)]
struct UnspecifiedSecurity {
    ssid: String,
    psk: Option<String>,
}

#[derive(Deserialize, Debug)]
struct Wpa2Security {
    ssid: String,
    psk: String,
}

impl<'de> Deserialize<'de> for ConnectRequest {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        #[derive(Deserialize)]
        #[serde(field_identifier, rename_all = "lowercase")]
        enum Tag {
            Wpa2,
            Other(String),
        }

        let v = Value::deserialize(deserializer)?;
        match Option::deserialize(&v["security"]).map_err(de::Error::custom)? {
            Some(Tag::Wpa2) => {
                let inner = Wpa2Security::deserialize(v).map_err(de::Error::custom)?;
                Ok(ConnectRequest::Wpa2(inner))
            }
            None => {
                let inner = UnspecifiedSecurity::deserialize(v).map_err(de::Error::custom)?;
                Ok(ConnectRequest::Unspecified(inner))
            }
            Some(Tag::Other(other)) => {
                const VARIANTS: &[&str] = &["wpa2"];
                Err(de::Error::unknown_variant(&other, VARIANTS))
            }
        }
    }
}

fn main() {
    let present = r#"{"ssid": "foo", "psk": "bar", "security": "wpa2"}"#;
    println!("{:?}", serde_json::from_str::<ConnectRequest>(&present).unwrap());

    let missing = r#"{"ssid": "foo", "psk": "bar"}"#;
    println!("{:?}", serde_json::from_str::<ConnectRequest>(&missing).unwrap());

    let unknown = r#"{"ssid": "foo", "psk": "bar", "security": "unrecognized"}"#;
    println!("{}", serde_json::from_str::<ConnectRequest>(&unknown).unwrap_err());
}

@dtolnay dtolnay closed this as completed Apr 19, 2018
@saethlin
Copy link
Contributor

saethlin commented Jun 6, 2018

I'm currently staring down a situation just like what's described above but my enum may have as many as 73 variants. Deserializing it is also showing up in my profile, so I'm a bit bothered by the bookkeeping of the approach you've suggested above as well as what looks like a possible performance penalty from deserializing twice (I'd like to measure but I don't have an alternate approach).

Is there anything more to your simple explanation above? Do you plan on supporting a #serde[ attribute to support this pattern?

@dtolnay
Copy link
Member Author

dtolnay commented Jun 6, 2018

The trouble with internally tagged enums is that in general you may receive many fields of data before the tag, and those fields can only be meaningfully interpreted after knowing the tag. So it seems inherent that you would need to do two passes to deserialize fully. If your data always contains the tag field first, you may be able to do better. Or if the variants have many fields in common then you may be able to handle those fields better.

kkovaacs added a commit to eqlabs/pathfinder that referenced this issue Aug 30, 2022
Deserialization for invoke transactions is now done by hand, since
the `version` property is optional. Serde doesn't support deriving
deserialize for internally tagged enums where the tag property may
be missing: serde-rs/serde#1221

Unfortunately `InvokeTransaction::deserialize` now makes multiple
deserialization passes on the data...
kkovaacs added a commit to eqlabs/pathfinder that referenced this issue Aug 30, 2022
Deserialization for invoke transactions is now done by hand, since
the `version` property is optional. Serde doesn't support deriving
deserialize for internally tagged enums where the tag property may
be missing: serde-rs/serde#1221

Unfortunately `InvokeTransaction::deserialize` now makes multiple
deserialization passes on the data...
kkovaacs added a commit to eqlabs/pathfinder that referenced this issue Aug 31, 2022
Deserialization for invoke transactions is now done by hand, since
the `version` property is optional. Serde doesn't support deriving
deserialize for internally tagged enums where the tag property may
be missing: serde-rs/serde#1221

Unfortunately `InvokeTransaction::deserialize` now makes multiple
deserialization passes on the data...
kkovaacs added a commit to eqlabs/pathfinder that referenced this issue Aug 31, 2022
Deserialization for invoke transactions is now done by hand, since
the `version` property is optional. Serde doesn't support deriving
deserialize for internally tagged enums where the tag property may
be missing: serde-rs/serde#1221

Unfortunately `InvokeTransaction::deserialize` now makes multiple
deserialization passes on the data...
kkovaacs added a commit to eqlabs/pathfinder that referenced this issue Sep 1, 2022
Deserialization for invoke transactions is now done by hand, since
the `version` property is optional. Serde doesn't support deriving
deserialize for internally tagged enums where the tag property may
be missing: serde-rs/serde#1221

Unfortunately `InvokeTransaction::deserialize` now makes multiple
deserialization passes on the data...
kkovaacs added a commit to eqlabs/pathfinder that referenced this issue Sep 1, 2022
Deserialization for invoke transactions is now done by hand, since
the `version` property is optional. Serde doesn't support deriving
deserialize for internally tagged enums where the tag property may
be missing: serde-rs/serde#1221

Unfortunately `InvokeTransaction::deserialize` now makes multiple
deserialization passes on the data...
kkovaacs added a commit to eqlabs/pathfinder that referenced this issue Sep 1, 2022
Deserialization for invoke transactions is now done by hand, since
the `version` property is optional. Serde doesn't support deriving
deserialize for internally tagged enums where the tag property may
be missing: serde-rs/serde#1221

Unfortunately `InvokeTransaction::deserialize` now makes multiple
deserialization passes on the data...
kkovaacs added a commit to eqlabs/pathfinder that referenced this issue Sep 1, 2022
Deserialization for invoke transactions is now done by hand, since
the `version` property is optional. Serde doesn't support deriving
deserialize for internally tagged enums where the tag property may
be missing: serde-rs/serde#1221

Unfortunately `InvokeTransaction::deserialize` now makes multiple
deserialization passes on the data...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

No branches or pull requests

2 participants