How can I support an unknown or other value for a Serde enum?

前端 未结 3 672
野的像风
野的像风 2021-01-14 01:09

I have a JSON API that returns an object that looks like this:

{
  \"PrivatePort\": 2222,
  \"PublicPort\": 3333,
  \"Type\": \"tcp\"
}

To

相关标签:
3条回答
  • 2021-01-14 01:23

    Simple case should be fine with this:

    use serde::de::Visitor;
    use serde::{Deserialize, Deserializer, Serialize};
    use serde_json::from_str;
    
    #[derive(Deserialize, Serialize, Debug)]
    #[serde(rename_all = "PascalCase")]
    pub struct PortMapping {
        pub private_port: u16,
        pub public_port: u16,
        #[serde(rename = "Type")]
        pub port_type: PortType,
    }
    
    #[derive(Clone, Eq, PartialEq, Serialize, Debug)]
    pub enum PortType {
        Sctp,
        Tcp,
        Udp,
        Unknown(String),
    }
    
    const PORT_TYPE: &'static [(&'static str, PortType)] = &[
        ("sctp", PortType::Sctp),
        ("tcp", PortType::Tcp),
        ("udp", PortType::Udp),
    ];
    
    impl From<String> for PortType {
        fn from(variant: String) -> Self {
            PORT_TYPE
                .iter()
                .find(|(id, _)| *id == &*variant)
                .map(|(_, port_type)| port_type.clone())
                .unwrap_or(PortType::Unknown(variant))
        }
    }
    
    impl<'a> From<&'a str> for PortType {
        fn from(variant: &'a str) -> Self {
            PORT_TYPE
                .iter()
                .find(|(id, _)| *id == &*variant)
                .map(|(_, port_type)| port_type.clone())
                .unwrap_or_else(|| PortType::Unknown(variant.to_string()))
        }
    }
    
    impl<'de> Deserialize<'de> for PortType {
        fn deserialize<D>(de: D) -> Result<PortType, D::Error>
        where
            D: Deserializer<'de>,
        {
            struct PortTypeVisitor {}
    
            impl<'de> Visitor<'de> for PortTypeVisitor {
                type Value = PortType;
    
                fn expecting(
                    &self,
                    fmt: &mut std::fmt::Formatter<'_>,
                ) -> std::result::Result<(), std::fmt::Error> {
                    fmt.write_str("We expected a string")
                }
    
                fn visit_str<E>(self, variant: &str) -> Result<Self::Value, E> {
                    Ok(variant.into())
                }
    
                fn visit_string<E>(self, variant: String) -> Result<Self::Value, E> {
                    Ok(variant.into())
                }
            }
    
            de.deserialize_string(PortTypeVisitor {})
        }
    }
    
    fn main() {
        let input = r#"
        {
          "PrivatePort": 2222,
          "PublicPort": 3333,
          "Type": "dccp"
        }
        "#;
    
        let result: Result<PortMapping, _> = from_str(input);
    
        println!("{:#?}", result);
    }
    

    I don't think there is a idiomatic way to do this, that could be included in the future.

    0 讨论(0)
  • 2021-01-14 01:31

    There's an issue for this, though it's been open for 3 years with no full resolution so far. Serde #912.

    What seems to be currently implemented (though undocumented) at the time of this post is #[serde(other)]. It can only be applied to unit enum fields, which limits its usefulness:

    #[derive(Deserialize, PartialEq)]
    #[serde(tag = "tag")]
    enum Target {
       A(()),
       B(()),
       #[serde(other)]
       Others
    }
    
    fn main() {
        assert_eq!(Target::Others, from_str::<Target>(r#"{ "tag": "blablah" }"#).unwrap());
    }
    

    Other than that, the only other method as of this writing is writing your own Deserialize implementation.

    0 讨论(0)
  • 2021-01-14 01:42

    I do it with serde(from="String")

    #[derive(Eq, PartialEq, Deserialize, Serialize, Debug)]
    #[serde(rename_all = "snake_case", from="String")]
    pub enum PortType {
        Sctp,
        Tcp,
        Udp,
        Unknown(String),
    }
    
    impl From<String> for PortType {
        fn from(s: String)->Self {
            use PortType::*;
    
            return match s.as_str() {
                "sctp" => Sctp,
                "tcp" => Tcp,
                "udp" => Udp,
                _ => Unknown(s)
            }
        }
    }
    
    #[derive(Deserialize, Serialize, Debug)]
    #[serde(rename_all = "PascalCase")]
    pub struct PortMapping {
        pub private_port: u16,
        pub public_port: u16,
        #[serde(rename = "Type")]
        pub port_type: PortType,
    }
    
    0 讨论(0)
提交回复
热议问题