Help me create this serde model

I have this enum:

enum MyEnum {
  Foo,
  Bar,
  List(Vec<String>),
}

I want to map:

  • "foo"MyEnum::Foo.
  • "bar"MyEnum::Bar.
  • ["a", "b", "c"]MyEnum::List(vec!["a".to_string(), "b".to_string(), "c".to_string()]).

With #[serde(untagged), I can archive the List variant, but Foo and Bar are turned into null.

You need to split the enum for it.

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
enum FooBar {
    Foo,
    Bar,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum MyEnum {
    FooBar(FooBar),
    List(Vec<String>),
}

Your suggestion would require me to change API. I want to keep the enum structure.

You can use the version suggested above internally and implement Serialize and Deserialize for your enum by delegating to the derived implementation of the internal types.

You can do a custom implementation of serialize and deserialize. The deserialize implementation was a little more complicated than I thought, but it does exactly what you wanted!

mod myenum_serde {
    use super::MyEnum;
    use serde::{de, ser, Deserialize, Deserializer, Serialize, Serializer};

    impl Serialize for MyEnum {
        fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
            use ser::SerializeSeq;
            match self {
                MyEnum::Foo => s.serialize_str("foo"),
                MyEnum::Bar => s.serialize_str("bar"),
                MyEnum::List(v) => {
                    let mut seq = s.serialize_seq(Some(v.len()))?;
                    for item in v {
                        seq.serialize_element(item)?;
                    }
                    seq.end()
                }
            }
        }
    }

    struct MyEnumVisitor;
    impl<'de> de::Visitor<'de> for MyEnumVisitor {
        type Value = MyEnum;

        fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
            write!(formatter, r#"Either, "foo", "bar", or a list of strings"#)
        }

        fn visit_str<E: de::Error>(self, s: &str) -> Result<Self::Value, E> {
            match s {
                "foo" => Ok(MyEnum::Foo),
                "bar" => Ok(MyEnum::Bar),
                _ => Err(de::Error::invalid_value(de::Unexpected::Str(s), &self)),
            }
        }

        fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
            let mut v = if let Some(size) = seq.size_hint() {
                Vec::with_capacity(size)
            } else {
                Vec::new()
            };

            while let Some(element) = seq.next_element()? {
                v.push(element);
            }

            Ok(MyEnum::List(v))
        }
    }

    impl<'de> Deserialize<'de> for MyEnum {
        fn deserialize<D: Deserializer<'de>>(d: D) -> Result<MyEnum, D::Error> {
            d.deserialize_any(MyEnumVisitor)
        }
    }
}

#[derive(Debug)]
enum MyEnum {
    Foo,
    Bar,
    List(Vec<String>),
}

fn main() {
    println!("Serialize");
    println!("---------");
    println!("{}", serde_json::to_string(&MyEnum::Foo).unwrap());
    println!("{}", serde_json::to_string(&MyEnum::Bar).unwrap());
    println!(
        "{}",
        serde_json::to_string(&MyEnum::List(vec![
            "a".to_string(),
            "b".to_string(),
            "c".to_string()
        ]))
        .unwrap()
    );
    println!();
    println!("Deserialize");
    println!("----------");
    println!("{:?}", serde_json::from_str::<MyEnum>(r#""foo""#).unwrap());
    println!("{:?}", serde_json::from_str::<MyEnum>(r#""bar""#).unwrap());
    println!(
        "{:?}",
        serde_json::from_str::<MyEnum>(r#"["a", "b", "c"]"#).unwrap()
    );
    println!("Graceful failure: \"green\"");
    println!("{:?}", serde_json::from_str::<MyEnum>(r#""green""#));
}
Serialize
---------
"foo"
"bar"
["a","b","c"]

Deserialize
----------
Foo
Bar
List(["a", "b", "c"])
Graceful failure: "green"
Err(Error("invalid value: string \"green\", expected Either, \"foo\", \"bar\", or a list of strings", line: 1, column: 7))

It even has a nice error message! Serde is cool.

4 Likes