I need to manually de/serialize an enum using a custom Deserialize impl.
The problem is, I have no clue how to go about the deserialization step, and serde.rs is no help either unfortunately, because while it has some examples for structs, it has no examples for enums.
So if we assume an enum like this, with the given serde::Serialize impl:
pub enum Foo {
Bar { f0: String, f1: usize },
Baz { f0: usize },
}
impl serde::Serialize for Foo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer
{
use serde::ser::SerializeStructVariant;
match self {
Self::Bar { f0, f1 } => {
let mut ser = serializer.serialize_struct_variant("Foo", 0, "Bar", 2)?;
ser.serialize_field("f0", &f0)?;
ser.serialize_field("f1", &f1)?;
ser.end()
},
Self::Baz { f0 } => {
let mut ser = serializer.serialize_struct_variant("Foo", 1, "Baz", 1)?;
ser.serialize_field("f0", &f0)?;
ser.end()
},
}
}
}
How can I manually write a serde::Deserialize<'de> impl for it?
The interface for Visitor seems pretty self-explanatory: it has a visit_enum method that accepts a sub-deserializer implementing EnumAccess. You have to implement this method and call the EnumAccess for retrieving a VariantAccess, just like you would do with SeqAccess or MapAccess.
You may want to additionally implement the visitor methods for maps, strings, and integers, to support deserializing from a dynamic/self-describing value.
The problem is with EnumAccess: docs.rs makes it as clear as mud how to use it.
It has a variant method but there's no description of what's what, where I'm supposed to get a Visitor instance from for the methods of the associated Variant type, what V is supposed to be, etc.
It's just all little more than type-level syntax soup, a problem e.g. Diesel also has.
Deserializing an enum is quite involved because of the handling for determining which variant to deserialize. The quickest way to get a basic intuition for code shape is to cargo expand a derived deserialization impl for a similarly shaped enum.
Note that it basically only has a single method, because the two methods only represent the usual seeded and non-seeded flavors, something that you almost never need to worry about.
The way you get visitors is you construct them yourself. That's how you get started in the first place when writing a custom Deserialize impl.
In the documentation, here, if you click through to "Writing a data format" and then "Implementing a deserializer", and search for "EnumAccess", you will see the explanation that the "value" is what helps you determine which variant you want. Then, you'd call the appropriate method on the VariantAccess.
Based on this information, I think a simple implementation with your example enum would look like this.
An optimized implementation that avoids the allocations, as suggested in the comments of the previous snippet, is here.
Deserializing an enum is complicated because it's essentially a recursive problem â you need to deserialize the discriminant identifier first and then deserialize the variant payload. If we hide the recursive nature behind more derives and only manually implement the top layer, it might look like:
pub enum Foo {
Bar { f0: String, f1: usize },
Baz { f0: usize },
}
impl<'de> Deserialize<'de> for Foo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: de::Deserializer<'de>,
{
// just derive the complexity away :D
#[derive(Deserialize)]
enum Foo_Discriminant { Bar, Baz }
#[derive(Deserialize)]
struct Foo_Bar { f0: String, f1: usize }
#[derive(Deserialize)]
struct Foo_Baz { f0: usize }
struct FooVisitor;
impl<'de> de::Visitor<'de> for FooVisitor {
type Value = Foo;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "enum Foo")
}
fn visit_enum<A>(self, data: A) -> Result<Self::Value, A::Error>
where
A: de::EnumAccess<'de>,
{
match data.variant()? {
(Foo_Discriminant::Bar, variant) => {
// unfortunately not real; c.f. EnumAccessDeserializer
let d = de::value::VariantAccessDeserializer::new(variant);
let Foo_Bar { f0, f1 } = Foo_Bar::deserialize(d)?;
Ok(Foo::Bar { f0, f1 })
}
(Foo_Discriminant::Baz, variant) => {
let d = de::value::VariantAccessDeserializer::new(variant);
let Foo_Bar { f0, f1 } = Foo_Bar::deserialize(d)?;
Ok(Foo::Bar { f0, f1 })
}
}
}
}
deserializer.deserialize_enum("Foo", &["Bar", "Baz"], FooVisitor)
}
}
Unfortunately serde doesn't at this time[1] provide a VariantAccessDeserializer to allow hopping back to the derivable Deserialize in this manner, so you are required to provide the Visitors for each variant's deserialization directly. But the reason I present it this way is that the Visitor impl you need to write is the one you would write for these composite parts. If you don't care about deserialization being as robust and zero-copy as a serde_derive derived impl, you don't need to write something as involved as its.
I'm not certain it's actually possible to provide a VariantAccessDeserializer shim with how serde's plumbing is structured, but it illustrates the purpose well here. âŠī¸
Thanks, these ultimately proved helpful.
As you may have guessed, the real case is significantly more complex.
Yeah I never would have thought to look there, as I wasn't attempting to implement a data format.
This is ultimately what caused me headaches. I knew it had to be done somehow, but didn't know how, and how it tied into the rest of the deserialization process.
This would have certainly been nice to have. Ah well, job's done now
In this case not really, since I'm interning the deserialized value immediately anyway.