dtolnay / typetag

Serde serializable and deserializable trait objects
Apache License 2.0
1.19k stars 38 forks source link

Allow the registry to be reused #19

Closed Rua closed 2 years ago

Rua commented 4 years ago

I find the crate very useful but it doesn't quite do what I want. I would like to implement a custom deserialisation that deserialises a map of TypeName:Value pairs into a Vec<Box<dyn Trait>>. In other words, a variant where externally::TaggedVisitor::visit_map reads multiple pairs instead of just one and returns a Vec<Box<T>>. However, there are next to no public interfaces in the crate, which makes it a "take it or leave it" deal.

Having delved into the source code to figure out what's going on, I found that there is a registry that automatically gets filled with one deserialiser for each implementing type, indexed by name. Being able to access that would allow me to implement the rest myself. Alternatively, if you can think of some other way to enable me to do what I want, that would be fine too, but this seems like the way that requires the least changes.

dtolnay commented 4 years ago

I won't expose the registry, but something like this should work for your use case:

use serde::de::value::MapAccessDeserializer;
use serde::de::{Deserialize, DeserializeSeed, Deserializer, IntoDeserializer, MapAccess, Visitor};
use std::fmt;
use std::marker::PhantomData;

pub fn trait_objects_from_map<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
where
    D: Deserializer<'de>,
    T: Deserialize<'de>,
{
    struct TraitObjectsVisitor<T>(PhantomData<T>);

    impl<'de, T> Visitor<'de> for TraitObjectsVisitor<T>
    where
        T: Deserialize<'de>,
    {
        type Value = Vec<T>;

        fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
            f.write_str("a map in which each TypeName:Value pair specifies a trait object")
        }

        fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
        where
            M: MapAccess<'de>,
        {
            let mut trait_objects = Vec::new();
            while let Some(key) = map.next_key()? {
                trait_objects.push(T::deserialize(MapAccessDeserializer::new(MapEntry {
                    key: Some(key),
                    value: &mut map,
                }))?);
            }
            Ok(trait_objects)
        }
    }

    struct MapEntry<M> {
        key: Option<String>,
        value: M,
    }

    impl<'de, M> MapAccess<'de> for MapEntry<M>
    where
        M: MapAccess<'de>,
    {
        type Error = M::Error;

        fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, M::Error>
        where
            K: DeserializeSeed<'de>,
        {
            self.key
                .take()
                .map(|key| seed.deserialize(key.into_deserializer()))
                .transpose()
        }

        fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, M::Error>
        where
            V: DeserializeSeed<'de>,
        {
            self.value.next_value_seed(seed)
        }
    }

    let visitor = TraitObjectsVisitor(PhantomData);
    deserializer.deserialize_map(visitor)
}

Used as:

use serde::Deserialize;
use std::fmt::Debug;

#[typetag::deserialize]
trait WebEvent: Debug {
    fn inspect(&self);
}

#[derive(Deserialize, Debug)]
struct PageLoad;

#[typetag::deserialize]
impl WebEvent for PageLoad {
    fn inspect(&self) {
        println!("200 milliseconds or bust");
    }
}

#[derive(Deserialize, Debug)]
struct Click {
    x: i32,
    y: i32,
}

#[typetag::deserialize]
impl WebEvent for Click {
    fn inspect(&self) {
        println!("negative space between the ads: x={} y={}", self.x, self.y);
    }
}

#[derive(Deserialize, Debug)]
struct Rua {
    #[serde(deserialize_with = "trait_objects_from_map")]
    objects: Vec<Box<dyn WebEvent>>,
}

fn main() {
    let input = r#"{"objects": {"Click": {"x":1,"y":1}, "PageLoad": null}}"#;
    println!("{:#?}", serde_json::from_str::<Rua>(input).unwrap());
}
Rua commented 4 years ago

So I guess I am a struct now. Thank you so so much for this, this works just as I wanted! It never ceases to amaze me how flexible serde is, but it also takes quite a bit to understand what exactly it's doing.