Repository: jonasbb/serde_with (rust)

Issue:
Suggestion(Seq): Map to Vector of structs
For structs like:
```rust
struct TupSt<K,V>(K,V);
struct OdnSt<K,V>{
    custom_key: K,
    v: V,
    // #[serde(skip)]
    // serde_skipped_fields
}
```
And implemented `From<(K,V)>` and `Into<(K,V)>`, allow them to be used inner type of `Seq`.

## Expected behaviour
```rust
#[serde_as]
pub struct Data {
    #[serde_as(as = "Seq<TupSt<_,_>>")]
    tuple_map: BTreeMap<i32,i32>,
    #[serde_as(as = "Seq<OdnSt<_,_>>")]
    ordinary_map: BTreeMap<i32,i32>,
}
```
Can be serialized into (or deserialized from):
```json
{
    "tuple_map": [
        {"key": 1, "value": 1},
        {"key": 2, "value": 2},
        {"key": 3, "value": 3},
        {"key": 4, "value": 4}
    ],
    "ordinary_map": [
         {"custom_key": 1, "v": 1},
         {"custom_key": 2, "v": 2},
         {"custom_key": 3, "v": 3},
         {"custom_key": 4, "v": 4},
    ]
}
```
## Motivation
I've been trying to implement reading from `Iceberg`'s `AVRO` files, and using `apache-avro` Serde API to read `Iceberg` encoded Maps will produce an array of small, key-value with naming tuples.

Pull request:
Generalize the trait bounds of `Seq`
Generalize the trait bounds of `Seq` to allow for more flexible. Instead of forcing the inner type to be a tuple, take anything convertible to/from a tuple. This can be expressed using the

Closes #565

Patch:
diff --git a/serde_with/CHANGELOG.md b/serde_with/CHANGELOG.md
index 348c20f..0ba9387 100644
--- a/serde_with/CHANGELOG.md
+++ b/serde_with/CHANGELOG.md
@@ -44,6 +44,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
     },
     ```
 
+### Changed
+
+* Relax the trait bounds of `Seq` to allow for more custom types. (#565)
+    This extends the support beyond tuples.
+
 ### Fixed
 
 * `EnumMap` passes the `human_readable` status of the `Serializer` to more places.
diff --git a/serde_with/src/de/impls.rs b/serde_with/src/de/impls.rs
index 92fbf83..fc8e953 100644
--- a/serde_with/src/de/impls.rs
+++ b/serde_with/src/de/impls.rs
@@ -545,25 +545,23 @@ 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
 
 #[cfg(feature = "alloc")]
 macro_rules! map_as_tuple_seq_intern {
-    ($tyorig:ident < K $(: $kbound1:ident $(+ $kbound2:ident)*)*, V $(, $typaram:ident : $bound1:ident $(+ $bound2:ident)*)* >, $ty:ident <(KAs, VAs)>) => {
-        impl<'de, K, KAs, V, VAs> DeserializeAs<'de, $tyorig<K, V>> for $ty<(KAs, VAs)>
+    ($tyorig:ident < K $(: $kbound1:ident $(+ $kbound2:ident)*)*, V $(, $typaram:ident : $bound1:ident $(+ $bound2:ident)*)* >, $ty:ident <TAs>) => {
+        impl<'de, K, V, TAs> DeserializeAs<'de, $tyorig<K, V>> for $ty<TAs>
         where
-            KAs: DeserializeAs<'de, K>,
-            VAs: DeserializeAs<'de, V>,
+            TAs: DeserializeAs<'de, (K, V)>,
             $(K: $kbound1 $(+ $kbound2)*,)*
         {
             fn deserialize_as<D>(deserializer: D) -> Result<$tyorig<K, V>, D::Error>
             where
                 D: Deserializer<'de>,
             {
-                struct SeqVisitor<K, KAs, V, VAs> {
-                    marker: PhantomData<(K, KAs, V, VAs)>,
+                struct SeqVisitor<K, V, TAs> {
+                    marker: PhantomData<(K, V, TAs)>,
                 }
 
-                impl<'de, K, KAs, V, VAs> Visitor<'de> for SeqVisitor<K, KAs, V, VAs>
+                impl<'de, K, V, TAs> Visitor<'de> for SeqVisitor<K, V, TAs>
                 where
-                    KAs: DeserializeAs<'de, K>,
-                    VAs: DeserializeAs<'de, V>,
+                TAs: DeserializeAs<'de, (K, V)>,
                     $(K: $kbound1 $(+ $kbound2)*,)*
                 {
                     type Value = $tyorig<K, V>;
@@ -579,17 +577,13 @@ macro_rules! map_as_tuple_seq_intern {
                     {
                         let iter = utils::SeqIter::new(access);
                         iter.map(|res| {
-                            res.map(
-                                |(k, v): (DeserializeAsWrap<K, KAs>, DeserializeAsWrap<V, VAs>)| {
-                                    (k.into_inner(), v.into_inner())
-                                },
-                            )
+                            res.map(<DeserializeAsWrap<(K, V), TAs>>::into_inner)
                         })
                         .collect()
                     }
                 }
 
-                let visitor = SeqVisitor::<K, KAs, V, VAs> {
+                let visitor = SeqVisitor::<K, V, TAs> {
                     marker: PhantomData,
                 };
                 deserializer.deserialize_seq(visitor)
@@ -600,9 +594,9 @@ macro_rules! map_as_tuple_seq_intern {
 #[cfg(feature = "alloc")]
 macro_rules! map_as_tuple_seq {
     ($tyorig:ident < K $(: $kbound1:ident $(+ $kbound2:ident)*)*, V $(, $typaram:ident : $bound1:ident $(+ $bound2:ident)*)* >) => {
-        map_as_tuple_seq_intern!($tyorig < K $(: $kbound1 $(+ $kbound2)*)* , V $(, $typaram : $bound1 $(+ $bound2)*)* > , Seq<(KAs, VAs)>);
+        map_as_tuple_seq_intern!($tyorig < K $(: $kbound1 $(+ $kbound2)*)* , V $(, $typaram : $bound1 $(+ $bound2)*)* > , Seq<TAs>);
         #[cfg(feature = "alloc")]
-        map_as_tuple_seq_intern!($tyorig < K $(: $kbound1 $(+ $kbound2)*)* , V $(, $typaram : $bound1 $(+ $bound2)*)* >, Vec<(KAs, VAs)>);
+        map_as_tuple_seq_intern!($tyorig < K $(: $kbound1 $(+ $kbound2)*)* , V $(, $typaram : $bound1 $(+ $bound2)*)* >, Vec<TAs>);
     }
 }
 foreach_map!(map_as_tuple_seq);
diff --git a/serde_with/src/lib.rs b/serde_with/src/lib.rs
index 0611653..58e1ff2 100644
--- a/serde_with/src/lib.rs
+++ b/serde_with/src/lib.rs
@@ -2099,9 +2099,6 @@ pub struct StringWithSeparator<Sep, T>(PhantomData<(Sep, T)>);
 /// However, sometimes this is not possible due to type constraints, e.g., if the type implements neither [`Hash`] nor [`Ord`].
 /// Another use case is deserializing a map with duplicate keys.
 ///
-/// The implementation is generic using the [`FromIterator`] and [`IntoIterator`] traits.
-/// Therefore, all of [`Vec`], [`VecDeque`](std::collections::VecDeque), and [`LinkedList`](std::collections::LinkedList) and anything which implements those are supported.
-///
 /// # Examples
 ///
 /// `Wrapper` does not implement [`Hash`] nor [`Ord`], thus prohibiting the use [`HashMap`] or [`BTreeMap`].
@@ -2150,13 +2147,14 @@ pub struct StringWithSeparator<Sep, T>(PhantomData<(Sep, T)>);
 /// ```
 pub struct Map<K, V>(PhantomData<(K, V)>);
 
-/// De/Serialize a Map into a list of tuples
+/// De/Serialize a Map into a list of tuples or custom type
 ///
 /// Some formats, like JSON, have limitations on the types of keys for maps.
 /// In case of JSON, keys are restricted to strings.
 /// Rust features more powerful keys, for example tuples, which can not be serialized to JSON.
 ///
 /// This helper serializes the Map into a list of tuples, which do not have the same type restrictions.
+/// Other types can be used to, if they implement `SerializeAs<(K, V)>`.
 ///
 /// # Examples
 ///
@@ -2195,6 +2193,89 @@ pub struct Map<K, V>(PhantomData<(K, V)>);
 /// assert_eq!(data, serde_json::from_value(value).unwrap());
 /// # }
 /// ```
+///
+/// ## Use a custom representation
+///
+/// ```rust
+/// # #[cfg(feature = "macros")] {
+/// # use serde::{Deserialize, Serialize};
+/// # use serde_json::json;
+/// # use serde_with::{serde_as, Seq};
+/// # use std::{collections::BTreeMap, net::IpAddr};
+/// #
+/// use serde_with::{de::DeserializeAsWrap, ser::SerializeAsWrap, DeserializeAs, SerializeAs};
+///
+/// #[derive(Serialize, Deserialize)]
+/// struct Custom<K, V> {
+///     custom_key: K,
+///     v: V,
+/// }
+///
+/// impl<K, KAs, V, VAs> SerializeAs<(K, V)> for Custom<KAs, VAs>
+/// where
+///     KAs: SerializeAs<K>,
+///     VAs: SerializeAs<V>,
+/// {
+///     fn serialize_as<S>((k, v): &(K, V), serializer: S) -> Result<S::Ok, S::Error>
+///     where
+///         S: serde::Serializer,
+///     {
+///         (Custom {
+///             custom_key: SerializeAsWrap::<K, KAs>::new(k),
+///             v: SerializeAsWrap::<V, VAs>::new(v),
+///         })
+///         .serialize(serializer)
+///     }
+/// }
+///
+/// impl<'de, K, KAs, V, VAs> DeserializeAs<'de, (K, V)> for Custom<KAs, VAs>
+/// where
+///     KAs: DeserializeAs<'de, K>,
+///     VAs: DeserializeAs<'de, V>,
+/// {
+///     fn deserialize_as<D>(deserializer: D) -> Result<(K, V), D::Error>
+///     where
+///         D: serde::Deserializer<'de>,
+///     {
+///         let c = <Custom<DeserializeAsWrap<K, KAs>, DeserializeAsWrap<V, VAs>>>::deserialize(
+///             deserializer,
+///         )?;
+///         Ok((c.custom_key.into_inner(), c.v.into_inner()))
+///     }
+/// }
+///
+/// #[serde_as]
+/// # #[derive(Debug, PartialEq)]
+/// #[derive(Serialize, Deserialize)]
+/// struct SM(#[serde_as(as = "Seq<Custom<_, _>>")] BTreeMap<u32, IpAddr>);
+///
+/// // This converts the Rust type
+/// let ip = "1.2.3.4".parse().unwrap();
+/// let ip2 = "255.255.255.255".parse().unwrap();
+/// let data = SM(vec![(1, ip), (10, ip), (200, ip2)].into_iter().collect());
+///
+/// // into this JSON
+/// let value = serde_json::json!(
+///     [
+///         {
+///             "custom_key": 1,
+///             "v": "1.2.3.4"
+///         },
+///         {
+///             "custom_key": 10,
+///             "v": "1.2.3.4"
+///         },
+///         {
+///             "custom_key": 200,
+///             "v": "255.255.255.255"
+///         }
+///     ]
+/// );
+///
+/// assert_eq!(value, serde_json::to_value(&data).unwrap());
+/// assert_eq!(data, serde_json::from_value(value).unwrap());
+/// # }
+/// ```
 pub struct Seq<V>(PhantomData<V>);
 
 /// Ensure no duplicate keys exist in a map.
diff --git a/serde_with/src/ser/impls.rs b/serde_with/src/ser/impls.rs
index 0075a8b..931f302 100644
--- a/serde_with/src/ser/impls.rs
+++ b/serde_with/src/ser/impls.rs
@@ -348,23 +348,21 @@ 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
 
 #[cfg(feature = "alloc")]
 macro_rules! map_as_tuple_seq_intern {
-    ($tyorig:ident < K, V $(, $typaram:ident : $bound:ident)* >, $ty:ident <(K, V)>) => {
-        impl<K, KAs, V, VAs $(, $typaram)*> SerializeAs<$tyorig<K, V $(, $typaram)*>> for $ty<(KAs, VAs)>
+    ($tyorig:ident < K, V $(, $typaram:ident : $bound:ident)* >, $ty:ident <TAs>) => {
+        impl<K, V, TAs $(, $typaram)*> SerializeAs<$tyorig<K, V $(, $typaram)*>> for $ty<TAs>
         where
-            KAs: SerializeAs<K>,
-            VAs: SerializeAs<V>,
+            for<'a> TAs: SerializeAs<(&'a K, &'a V)>,
             $($typaram: ?Sized + $bound,)*
         {
             fn serialize_as<S>(source: &$tyorig<K, V $(, $typaram)*>, serializer: S) -> Result<S::Ok, S::Error>
             where
                 S: Serializer,
             {
-                serializer.collect_seq(source.iter().map(|(k, v)| {
-                    (
-                        SerializeAsWrap::<K, KAs>::new(k),
-                        SerializeAsWrap::<V, VAs>::new(v),
-                    )
-                }))
+                let mut seq = serializer.serialize_seq(None)?;
+                for (k, v) in source {
+                    seq.serialize_element(&SerializeAsWrap::<(&K, &V), TAs>::new(&(k, v)))?;
+                }
+                seq.end()
             }
         }
     };
@@ -372,9 +370,9 @@ macro_rules! map_as_tuple_seq_intern {
 #[cfg(feature = "alloc")]
 macro_rules! map_as_tuple_seq {
     ($tyorig:ident < K, V $(, $typaram:ident : $bound:ident)* >) => {
-        map_as_tuple_seq_intern!($tyorig<K, V $(, $typaram: $bound)* >, Seq<(K, V)>);
+        map_as_tuple_seq_intern!($tyorig<K, V $(, $typaram: $bound)* >, Seq<TAs>);
         #[cfg(feature = "alloc")]
-        map_as_tuple_seq_intern!($tyorig<K, V $(, $typaram: $bound)* >, Vec<(K, V)>);
+        map_as_tuple_seq_intern!($tyorig<K, V $(, $typaram: $bound)* >, Vec<TAs>);
     }
 }
 foreach_map!(map_as_tuple_seq);


Test patch:
diff --git a/serde_with/tests/serde_as/map_tuple_list.rs b/serde_with/tests/serde_as/map_tuple_list.rs
index 8855ef7..f6c42b6 100644
--- a/serde_with/tests/serde_as/map_tuple_list.rs
+++ b/serde_with/tests/serde_as/map_tuple_list.rs
@@ -319,3 +319,74 @@ fn test_tuple_array_as_map() {
             }"#]],
     );
 }
+
+#[test]
+fn test_map_as_custom_struct() {
+    let ip = "1.2.3.4".parse().unwrap();
+    let ip2 = "255.255.255.255".parse().unwrap();
+
+    use serde_with::{de::DeserializeAsWrap, ser::SerializeAsWrap, DeserializeAs, SerializeAs};
+
+    #[derive(serde::Serialize, serde::Deserialize)]
+    struct Custom<K, V> {
+        custom_key: K,
+        v: V,
+    }
+
+    impl<K, KAs, V, VAs> SerializeAs<(K, V)> for Custom<KAs, VAs>
+    where
+        KAs: SerializeAs<K>,
+        VAs: SerializeAs<V>,
+    {
+        fn serialize_as<S>((k, v): &(K, V), serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: serde::Serializer,
+        {
+            (Custom {
+                custom_key: SerializeAsWrap::<K, KAs>::new(k),
+                v: SerializeAsWrap::<V, VAs>::new(v),
+            })
+            .serialize(serializer)
+        }
+    }
+
+    impl<'de, K, KAs, V, VAs> DeserializeAs<'de, (K, V)> for Custom<KAs, VAs>
+    where
+        KAs: DeserializeAs<'de, K>,
+        VAs: DeserializeAs<'de, V>,
+    {
+        fn deserialize_as<D>(deserializer: D) -> Result<(K, V), D::Error>
+        where
+            D: serde::Deserializer<'de>,
+        {
+            let c = <Custom<DeserializeAsWrap<K, KAs>, DeserializeAsWrap<V, VAs>>>::deserialize(
+                deserializer,
+            )?;
+            Ok((c.custom_key.into_inner(), c.v.into_inner()))
+        }
+    }
+
+    #[serde_as]
+    #[derive(Debug, Serialize, Deserialize, PartialEq)]
+    struct SM(#[serde_as(as = "Seq<Custom<_, _>>")] BTreeMap<u32, IpAddr>);
+
+    let map: BTreeMap<_, _> = vec![(1, ip), (10, ip), (200, ip2)].into_iter().collect();
+    is_equal(
+        SM(map),
+        expect![[r#"
+            [
+              {
+                "custom_key": 1,
+                "v": "1.2.3.4"
+              },
+              {
+                "custom_key": 10,
+                "v": "1.2.3.4"
+              },
+              {
+                "custom_key": 200,
+                "v": "255.255.255.255"
+              }
+            ]"#]],
+    );
+}