3

I have a type like this, although my actual type is bigger and more complex:

struct MyType {
    i: u32,
}

If I implement Deserialize for this type, serde looks for something like this (I'm interested in JSON):

{"i":100}  

I want to customize it so that I can deserialize from a byte array as well:

[1, 2, 3, 4]

I can write an impl to handle the array, but I want serde to auto generate the rest (which will be visit_map):

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

        impl<'de> Visitor<'de> for MyTypeVisitor {
            type Value = MyType;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                write!(formatter, "struct or array of 4 integers")
            }

            fn visit_seq<A: SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
                // ...
            }
        }

        // deserializer.deserialize_any(MyTypeVisitor)
    }
}

Is that possible? In this example, it's not difficult, but when the struct is large, hand writing deserialization can be painful.

This is not a duplicate of How to transform fields during deserialization using Serde? because deserialize_with works only for 1 field. I can't understand how I would make it work for my real type:

pub enum Component {
    String(StringComponent),
    Translation(TranslationComponent),
    Score(ScoreComponent),
    Selector(SelectorComponent),
}

pub struct StringComponent {
    #[serde(flatten)] pub base: Base,
    pub text: String,
}

pub struct Base {
    // ...
    extra: Option<Vec<Component>>,
    // ...
}

What I want to do is:

  • While deserializing, if the input is a number, return a Component::String. This can be done with visit_i/u/f64 and friends.
  • If the input is a string, return a Component::String again. This can be done with visit_str/string.
  • If input is an array [..], deserialize it as usual, but make assign elements in array[1..] to extra of array[0]. This can be done by visit_seq.
  • If input is a map, let serde derive handle it.
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
wingerse
  • 3,670
  • 1
  • 29
  • 61

1 Answers1

2

The Serde documentation has an example showing how to implement deserializing from either a string or a structure. This is equivalent to your case, just smaller.

The important part is this:

fn visit_map<M>(self, visitor: M) -> Result<T, M::Error>
where
    M: MapAccess<'de>,
{
    Deserialize::deserialize(de::value::MapAccessDeserializer::new(visitor))
}

This delegates to the built-in deserialization implementation. Since all of your other cases are custom, this should be suitable.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thank you <3. The trick was to derive Deserialize as usual for my type and make a wrapper type for the additional deserialization logic with that to delegate. – wingerse Mar 28 '18 at 22:51