Open kornelski opened 6 years ago
Yes, I know that. This is intentional. I did not have much time to think how to do this for enums also unfortunately.
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)
}
}
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.
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.
@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.
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!
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.
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
?
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.
I've tried:
but both cases are rejected.