jonasbb / serde_with

This crate provides custom de/serialization helpers to use in combination with serde's `with`-annotation and with the improved `serde_as`-annotation.
https://docs.rs/serde_with
Apache License 2.0
628 stars 66 forks source link

Deserialize a sequence from a map by ignoring keys #745

Open MingweiSamuel opened 3 months ago

MingweiSamuel commented 3 months ago

I have a case where I'd like to deserialize a heterogeneous tuple from a map by ignoring the keys of the map. I implemented DeserializeAs for IgnoreKeys based on the regular tuple implementation. Naturally you can only deserialize and cannot re-serialize as the key names are all lost.

IgnoreKeys ```rust use std::fmt; use std::marker::PhantomData; use serde::de::{Deserializer, Error as DeError, IgnoredAny, MapAccess, Visitor}; use serde_with::de::{DeserializeAs, DeserializeAsWrap}; /// Deserialize a tuple sequence from a map, ignoring keys. pub struct IgnoreKeys(PhantomData); macro_rules! tuple_impl { ($len:literal $($n:tt $t:ident $tas:ident)+) => { impl<'de, $($t, $tas,)+> DeserializeAs<'de, ($($t,)+)> for IgnoreKeys<($($tas,)+)> where $($tas: DeserializeAs<'de, $t>,)+ { fn deserialize_as(deserializer: D) -> Result<($($t,)+), D::Error> where D: Deserializer<'de>, { struct MapVisitor<$($t,)+>(PhantomData<($($t,)+)>); impl<'de, $($t, $tas,)+> Visitor<'de> for MapVisitor<$(DeserializeAsWrap<$t, $tas>,)+> where $($tas: DeserializeAs<'de, $t>,)+ { type Value = ($($t,)+); fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str(concat!("a map of size ", $len)) } #[allow(non_snake_case)] fn visit_map(self, mut map: A) -> Result where A: MapAccess<'de>, { $( let $t: (IgnoredAny, DeserializeAsWrap<$t, $tas>) = match map.next_entry()? { Some(value) => value, None => return Err(DeError::invalid_length($n, &self)), }; )+ Ok(($($t.1.into_inner(),)+)) } } deserializer.deserialize_map( MapVisitor::<$(DeserializeAsWrap<$t, $tas>,)+>(PhantomData), ) } } }; } tuple_impl!(1 0 T0 As0); tuple_impl!(2 0 T0 As0 1 T1 As1); tuple_impl!(3 0 T0 As0 1 T1 As1 2 T2 As2); tuple_impl!(4 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3); tuple_impl!(5 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4); tuple_impl!(6 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5); tuple_impl!(7 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6); tuple_impl!(8 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7); tuple_impl!(9 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8); tuple_impl!(10 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9); tuple_impl!(11 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10); tuple_impl!(12 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11); tuple_impl!(13 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12); tuple_impl!(14 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12 13 T13 As13); tuple_impl!(15 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12 13 T13 As13 14 T14 As14); tuple_impl!(16 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12 13 T13 As13 14 T14 As14 15 T15 As15); ```

Is there a better way to do this? And if not, should I make a PR to add this to the crate?

MingweiSamuel commented 3 months ago

Sorta similar to #541, except instead of only keeping homogeneous keys this keeps the heterogeneous values

jonasbb commented 3 months ago

I think this implementation looks fine as is. As long as you are aware of the limitations. Since you don't specify a type for the key (but have IgnoredAny), this only works with formats that are sufficiently self-describing to skip over unknown types (i.e., support deserialize_ignored_any).

It would be possible to make the key type another parameter, defaulting to IgnoredAny, but optional type parameters cannot be at the front, or rather need to be specified if any later type parameter is provided too.

There are a couple of extensions I can think to make it nicer to use. You might not want to specify a transformation at all, but instead just use the values. In that case, you could default to Same such that no types need to be provided. You would use it like:

#[serde_with::serde_as]
#[derive(Debug, serde::Deserialize)]
struct Foo {
    #[serde_as(as = "IgnoreKeys")]
    foo: (String, i32, String),
}

You could provide that by extending the macro to create a second impl for IgnoredKeys<Same>.

Implementation ```rust macro_rules! tuple_impl { ($len:literal $($n:tt $t:ident $tas:ident)+) => { impl<'de, $($t, $tas,)+> DeserializeAs<'de, ($($t,)+)> for IgnoreKeys<($($tas,)+)> where $($tas: DeserializeAs<'de, $t>,)+ { fn deserialize_as(deserializer: D) -> Result<($($t,)+), D::Error> where D: Deserializer<'de>, { struct MapVisitor<$($t,)+>(PhantomData<($($t,)+)>); impl<'de, $($t, $tas,)+> Visitor<'de> for MapVisitor<$(DeserializeAsWrap<$t, $tas>,)+> where $($tas: DeserializeAs<'de, $t>,)+ { type Value = ($($t,)+); fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str(concat!("a map of size ", $len)) } #[allow(non_snake_case)] fn visit_map(self, mut map: A) -> Result where A: MapAccess<'de>, { $( let $t: (IgnoredAny, DeserializeAsWrap<$t, $tas>) = match map.next_entry()? { Some(value) => value, None => return Err(DeError::invalid_length($n, &self)), }; )+ Ok(($($t.1.into_inner(),)+)) } } deserializer.deserialize_map( MapVisitor::<$(DeserializeAsWrap<$t, $tas>,)+>(PhantomData), ) } } impl<'de, $($t,)+> DeserializeAs<'de, ($($t,)+)> for IgnoreKeys where $($t: serde::Deserialize<'de>,)+ { fn deserialize_as(deserializer: D) -> Result<($($t,)+), D::Error> where D: Deserializer<'de>, { struct MapVisitor<$($t,)+>(PhantomData<($($t,)+)>); impl<'de, $($t,)+> Visitor<'de> for MapVisitor<$($t,)+> where $($t: serde::Deserialize<'de>,)+ { type Value = ($($t,)+); fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str(concat!("a map of size ", $len)) } #[allow(non_snake_case)] fn visit_map(self, mut map: A) -> Result where A: MapAccess<'de>, { $( let $t: (IgnoredAny, $t) = match map.next_entry()? { Some(value) => value, None => return Err(DeError::invalid_length($n, &self)), }; )+ Ok(($($t.1,)+)) } } deserializer.deserialize_map( MapVisitor::<$($t,)+>(PhantomData), ) } } }; } tuple_impl!(1 0 T0 As0); tuple_impl!(2 0 T0 As0 1 T1 As1); tuple_impl!(3 0 T0 As0 1 T1 As1 2 T2 As2); tuple_impl!(4 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3); tuple_impl!(5 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4); tuple_impl!(6 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5); tuple_impl!(7 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6); tuple_impl!(8 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7); tuple_impl!(9 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8); tuple_impl!(10 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9); tuple_impl!(11 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10); tuple_impl!(12 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11); tuple_impl!(13 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12); tuple_impl!(14 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12 13 T13 As13); tuple_impl!(15 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12 13 T13 As13 14 T14 As14); tuple_impl!(16 0 T0 As0 1 T1 As1 2 T2 As2 3 T3 As3 4 T4 As4 5 T5 As5 6 T6 As6 7 T7 As7 8 T8 As8 9 T9 As9 10 T10 As10 11 T11 As11 12 T12 As12 13 T13 As13 14 T14 As14 15 T15 As15); ```

Instead of only supporting heterogeneous collections, homogeneous collections would be a nice extension, similar to the #541 issue you found.

#[serde_with::serde_as]
#[derive(Debug, serde::Deserialize)]
struct Foo {
    #[serde_as(as = "IgnoreKeys<_>")]
    foo: Vec<String>,
}

This would need a single fully separate implementation to the macro.

Implementation ```rust impl<'de, T, TAs> DeserializeAs<'de, Vec> for IgnoreKeys where TAs: DeserializeAs<'de, T>, { fn deserialize_as(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, { struct MapVisitor(PhantomData); impl<'de, T, TAs> Visitor<'de> for MapVisitor> where TAs: DeserializeAs<'de, T>, { type Value = Vec; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a map") } fn visit_map(self, mut map: A) -> Result where A: MapAccess<'de>, { let mut res = Vec::new(); while let Some((_, value)) = map.next_entry::>()? { res.push(value.into_inner()); } Ok(res) } } deserializer.deserialize_map(MapVisitor::>(PhantomData)) } } ```