Skip to content
This repository has been archived by the owner on Mar 25, 2024. It is now read-only.

Fallback on default enum variant when no tag is present #338

Closed
NikolaLohinski opened this issue Oct 21, 2022 · 2 comments
Closed

Fallback on default enum variant when no tag is present #338

NikolaLohinski opened this issue Oct 21, 2022 · 2 comments

Comments

@NikolaLohinski
Copy link

NikolaLohinski commented Oct 21, 2022

Hi and thanks for the lib, especially the latest work on YAML tags which it makes it really useable!

I am trying to figure out if there is a way to have a « default » tag applied when none is defined but the expected target type is an enum wanting a tag to be defined.

In code, is there anyway to have this passing ?

use serde_yaml;
use serde::Deserialize;

fn main() {
   #[derive(Deserialize, Debug, PartialEq)]
   enum Enum<'a> {
       Variant(&'a str),
       Other(u32),
       Default(&'a str),
   }
   let file = r#"
   - !Variant test
   - !Other 42
   - !Default foo
   - bar # will default to !Default since no tag has been supplied
   "#;
   let result: Vec<Enum> = serde_yaml::from_str(file).unwrap();
   assert_eq!(
       result,
       vec![
           Enum::Variant("test"),
           Enum::Other(42),
           Enum::Default("foo"),
           Enum::Default("bar"),
       ]
   );
}

I basically want the YAML to be deserialized into an enum, where only some tags are specified, and the others “default” to one of the variants.

Thanks in advance !

@dtolnay
Copy link
Owner

dtolnay commented Oct 21, 2022

Here are 2 possible implementations. A conceptually simpler one where we just insert a Tag if there isn't one already present, but it can't deserialize borrowed values this way:

// [dependencies]
// serde = { version = "1.0.147", features = ["derive"] }
// serde_yaml = "0.9.14"

use serde::{Deserialize, Deserializer};

#[derive(Deserialize, Debug, PartialEq)]
#[serde(remote = "Self")]
enum Enum {
    Variant(String),
    Other(u32),
    Default(String),
}

impl<'de> Deserialize<'de> for Enum {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let tagged_value = match serde_yaml::Value::deserialize(deserializer)? {
            serde_yaml::Value::Tagged(tagged_value) => *tagged_value,
            value => serde_yaml::value::TaggedValue {
                tag: serde_yaml::value::Tag::new("Default"),
                value,
            },
        };
        Enum::deserialize(tagged_value).map_err(serde::de::Error::custom)
    }
}

or a slightly more verbose one that avoids allocation and supports borrowing:

use serde::de::value::{BorrowedStrDeserializer, EnumAccessDeserializer};
use serde::de::{Deserializer, EnumAccess, Visitor};
use serde::Deserialize;
use std::fmt;
use std::marker::PhantomData;

#[derive(Deserialize, Debug, PartialEq)]
#[serde(remote = "Self")]
enum Enum<'a> {
    Variant(&'a str),
    Other(u32),
    Default(&'a str),
}

impl<'de: 'a, 'a> Deserialize<'de> for Enum<'a> {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct EnumVisitor<'de>(PhantomData<fn() -> Enum<'de>>);

        impl<'de> Visitor<'de> for EnumVisitor<'de> {
            type Value = Enum<'de>;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("enum Enum")
            }

            fn visit_borrowed_str<E>(self, s: &'de str) -> Result<Self::Value, E>
            where
                E: serde::de::Error,
            {
                let de = BorrowedStrDeserializer::new(s);
                Deserialize::deserialize(de).map(Enum::Default)
            }

            fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
            where
                A: EnumAccess<'de>,
            {
                let de = EnumAccessDeserializer::new(data);
                Enum::deserialize(de)
            }
        }

        deserializer.deserialize_any(EnumVisitor(PhantomData))
    }
}

@NikolaLohinski
Copy link
Author

Thanks a lot, I got it working just fine for my use case with your instructions!

As I needed to do serialization as well I had to add to explicitly re-implement Serialize as follows

impl Serialize for Enum {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
      S: serde::Serializer
  {
      Enum::serialize(self, serializer)
  }
}

since adding #[serde(remote = "Self")] requires defining it in order to get back the default serialization behaviour.

Anyhow, thanks for the help ! 🙏

@dtolnay dtolnay closed this as completed Oct 24, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants