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

deserialize_struct_case_insensitive not compatible with enums #2

Open
kornelski opened this issue May 12, 2018 · 9 comments
Open

deserialize_struct_case_insensitive not compatible with enums #2

kornelski opened this issue May 12, 2018 · 9 comments

Comments

@kornelski
Copy link

I've tried:

#[serde(deserialize_with = "deserialize_struct_case_insensitive")]
pub enum Foo {
   Bar
}
pub enum Foo {
   #[serde(deserialize_with = "deserialize_struct_case_insensitive")]
   Bar
}

but both cases are rejected.

@iddm
Copy link
Owner

iddm commented May 12, 2018

Yes, I know that. This is intentional. I did not have much time to think how to do this for enums also unfortunately.

@kornelski
Copy link
Author

For the record, I've managed to implement it "the hard way" like this:

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

        impl<'a> Visitor<'a> for MyEnumVisitor {
            type Value = MyEnum;

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

            fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
                match v.to_lowercase().as_str() {
                    "variant_a" => Ok(MyEnum::A),
                    "variant_b" => Ok(MyEnum::B)
                    x => Err(de::Error::unknown_variant(x, &["a", "b"])),
                }
            }

            fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
                self.visit_str(&v)
            }
        }

        deserializer.deserialize_string(MyEnumVisitor)
    }
}

@iddm
Copy link
Owner

iddm commented May 12, 2018

Awesome! Perhaps we could use it :) I'll look what we can do some time later, not sure when. It looks it is not so difficult though. Thank you for posting your hardcoded solution, it is useful anyway.

@iddm
Copy link
Owner

iddm commented Mar 5, 2020

I've just checked again and I don't think I may come up with a generic solution. Custom deserialisation will always work (at least it should), but can't think of something generic here. Also, enums are different and so the deserialisation must be different for all the types of enums.

@mainrs
Copy link

mainrs commented Jul 7, 2020

@vityafx:
What if an enum must implement FromStr + Deserialize, and the FromStr implementation will be used to do a lowercase comparison. Maybe having the end-user depend on strum_macros to implement the FromStr might be a good idea. Not sure if this approach works in serde as I am not familiar with the internals to be honest.

@iddm
Copy link
Owner

iddm commented Jul 7, 2020

Of course, this will work, and I could have implemented it myself, but this is not a generic solution I am looking for. For example, such a way still won't allow such an enum to be deserialized:

enum A {
    A,
    B(u64)
    C(Box<A>),
    D(Vector<A>),
    // and all other "complex" varieties
}

If you look at the syn parser, the enums are always complex, and such enums will require hell ton of a work to specify all the possible ways of deserialisation. It may work, but then it won't work for the types which aren't just plain enums in C/C++ meaning. Perhaps, it can't be done at all.

As you and other people are looking forward to seeing the support of this macro even for plain enums, I will add it, but under a different name. I might need a couple of weeks, can't return to this project right now, unfortunately. Perhaps, I will take a look at it even sooner. So stay tuned. :)

And thanks for bringing this up, as I don't know how this crate is used, I only see it is a bit popular, but whenever I try to look at its' usage through going each dependent crate, it is quite poor, like only "deserialise_bool_from_anything" is used :) So if you have any other suggestions, please post those! Have a nice day!

@mainrs
Copy link

mainrs commented Jul 7, 2020

Thanks for taking your time! In my use-case right now, it is actually for the deserialize_struct_case_insensitive. I have a simple enum with variants that do not hold any additional data and want it to, well, be deserializable, independent of the string's case :)

Maybe something like deserialize_plain_enum_case_insensitive is a good fit. Maybe there is even an official name for these enums:

enum E {
 A, B, C, D
}

As of know, I just implemented the deserialization myself, the same way as @kornelski did above.

@yanns
Copy link

yanns commented Dec 4, 2023

For info, I've found a more generic way to ignore the case when deserializing enums with variants that do not hold any additional data: https://docs.rs/oauth2/latest/oauth2/helpers/fn.deserialize_untagged_enum_case_insensitive.html#
You can have a look at the source to see how it is implemented:

///
/// Serde case-insensitive deserializer for an untagged `enum`.
///
/// This function converts values to lowercase before deserializing as the `enum`. Requires the
/// `#[serde(rename_all = "lowercase")]` attribute to be set on the `enum`.
///
/// # Example
///
/// In example below, the following JSON values all deserialize to
/// `GroceryBasket { fruit_item: Fruit::Banana }`:
///
///  * `{"fruit_item": "banana"}`
///  * `{"fruit_item": "BANANA"}`
///  * `{"fruit_item": "Banana"}`
///
/// Note: this example does not compile automatically due to
/// [Rust issue #29286](https://github.com/rust-lang/rust/issues/29286).
///
/// ```
/// # /*
/// use serde::Deserialize;
///
/// #[derive(Deserialize)]
/// #[serde(rename_all = "lowercase")]
/// enum Fruit {
///     Apple,
///     Banana,
///     Orange,
/// }
///
/// #[derive(Deserialize)]
/// struct GroceryBasket {
///     #[serde(deserialize_with = "helpers::deserialize_untagged_enum_case_insensitive")]
///     fruit_item: Fruit,
/// }
/// # */
/// ```
///
pub fn deserialize_untagged_enum_case_insensitive<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
    T: Deserialize<'de>,
    D: Deserializer<'de>,
{
    use serde::de::Error;
    use serde_json::Value;
    T::deserialize(Value::String(
        String::deserialize(deserializer)?.to_lowercase(),
    ))
    .map_err(Error::custom)
}

What do you think about having this generic function in serde-aux ?

@iddm
Copy link
Owner

iddm commented Dec 6, 2023

I think it is already better to implement a proc-macro for that and use an attribute, and correctly, using the syn crate, generate the deserializing code. Or, perhaps, just a macro.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants