Search code examples
rustdeserializationserde

How do you deserialize a string with an enum where any other value deserializes into a newtype variant while preserving the string?


I have an enum similar to the following:

#[derive(Deserialize)]
enum ExampleEnum {
    #[serde(rename = "variant1-rename")]
    Variant1,
    #[serde(rename = "variant2-rename")]
    Variant2,
    Other(String),
}

It should deserialize like this:

Serialized Deserialized
"variant1-rename" ExampleEnum::Variant1
"variant2-rename" ExampleEnum::Variant2
"foobar" ExampleEnum::Other("foobar")

With my current code, the first two work as intended, but the last one does not, the program errors saying that it expected either "variant1-rename", "variant2-rename", or "Other".

It seems like the #[serde(other)] attribute would result in the desired behavior, but it can only be used with internally tagged or adjacently tagged enums.

The (JSON) data I am deserializing has a property that should be deserialized into the enum, so it would look like this:

{
    "property": "variant1-rename"
}

Solution

  • Here is my code that solved the problem, based off of Caesar's answer and the Manually implementing Deserialize for a struct page in serde's documentation.

    use serde::{ Deserialize, de };
    
    impl<'de> Deserialize<'de> for ExampleEnum {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
        where
            D: serde::Deserializer<'de>,
        {
            struct ExampleEnumVisitor;
    
            impl<'de> de::Visitor<'de> for ExampleEnumVisitor {
                type Value = ExampleEnum;
    
                fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                    formatter.write_str("`variant1-rename`, `variant2-rename`, or some other string")
                }
    
                fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
                where
                    E: de::Error,
                {
                    match v {
                        "variant1-rename" => Ok(ExampleEnum::Variant1),
                        "variant2-rename" => Ok(ExampleEnum::Variant2),
                        _ => Ok(ExampleEnum::Other(v.to_string())),
                    }
                }
            }
    
            deserializer.deserialize_identifier(ExampleEnumVisitor)
        }
    }
    

    If you wish for a shorter solution, use Caesar's. Read the comments under their solution to understand the differences.