Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deserialize a sequence from a map by ignoring keys #745

Open
MingweiSamuel opened this issue May 5, 2024 · 2 comments
Open

Deserialize a sequence from a map by ignoring keys #745

MingweiSamuel opened this issue May 5, 2024 · 2 comments

Comments

@MingweiSamuel
Copy link

MingweiSamuel commented May 5, 2024

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
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<T>(PhantomData<T>);

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<D>(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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
                    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
Copy link
Author

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

@jonasbb
Copy link
Owner

jonasbb commented May 6, 2024

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
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<D>(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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
                    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<serde_with::Same>
        where
            $($t: serde::Deserialize<'de>,)+
        {
            fn deserialize_as<D>(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<A>(self, mut map: A) -> Result<Self::Value, A::Error>
                    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
impl<'de, T, TAs> DeserializeAs<'de, Vec<T>> for IgnoreKeys<TAs>
where
    TAs: DeserializeAs<'de, T>,
{
    fn deserialize_as<D>(deserializer: D) -> Result<Vec<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 = Vec<T>;

            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter.write_str("a map")
            }

            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
            where
                A: MapAccess<'de>,
            {
                let mut res = Vec::new();
                while let Some((_, value)) = map.next_entry::<IgnoredAny, DeserializeAsWrap<T, TAs>>()? {
                    res.push(value.into_inner());
                }
                Ok(res)
            }
        }

        deserializer.deserialize_map(MapVisitor::<DeserializeAsWrap<T, TAs>>(PhantomData))
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants