Skip to content

Commit d038657

Browse files
authored
Implement MapSkipError, analogous to VecSkipError, but for map-like data structures. (#763)
2 parents 31e9172 + aaa0a29 commit d038657

File tree

12 files changed

+752
-107
lines changed

12 files changed

+752
-107
lines changed

serde_with/src/de/impls.rs

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -908,80 +908,6 @@ where
908908
}
909909
}
910910

911-
#[cfg(feature = "alloc")]
912-
impl<'de, T, U> DeserializeAs<'de, Vec<T>> for VecSkipError<U>
913-
where
914-
U: DeserializeAs<'de, T>,
915-
{
916-
fn deserialize_as<D>(deserializer: D) -> Result<Vec<T>, D::Error>
917-
where
918-
D: Deserializer<'de>,
919-
{
920-
enum GoodOrError<T, TAs> {
921-
Good(T),
922-
// Only here to consume the TAs generic
923-
Error(PhantomData<TAs>),
924-
}
925-
926-
impl<'de, T, TAs> Deserialize<'de> for GoodOrError<T, TAs>
927-
where
928-
TAs: DeserializeAs<'de, T>,
929-
{
930-
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
931-
where
932-
D: Deserializer<'de>,
933-
{
934-
let is_hr = deserializer.is_human_readable();
935-
let content: content::de::Content<'de> = Deserialize::deserialize(deserializer)?;
936-
937-
Ok(
938-
match <DeserializeAsWrap<T, TAs>>::deserialize(
939-
content::de::ContentDeserializer::<D::Error>::new(content, is_hr),
940-
) {
941-
Ok(elem) => GoodOrError::Good(elem.into_inner()),
942-
Err(_) => GoodOrError::Error(PhantomData),
943-
},
944-
)
945-
}
946-
}
947-
948-
struct SeqVisitor<T, U> {
949-
marker: PhantomData<T>,
950-
marker2: PhantomData<U>,
951-
}
952-
953-
impl<'de, T, TAs> Visitor<'de> for SeqVisitor<T, TAs>
954-
where
955-
TAs: DeserializeAs<'de, T>,
956-
{
957-
type Value = Vec<T>;
958-
959-
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
960-
formatter.write_str("a sequence")
961-
}
962-
963-
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
964-
where
965-
A: SeqAccess<'de>,
966-
{
967-
utils::SeqIter::new(seq)
968-
.filter_map(|res: Result<GoodOrError<T, TAs>, A::Error>| match res {
969-
Ok(GoodOrError::Good(value)) => Some(Ok(value)),
970-
Ok(GoodOrError::Error(_)) => None,
971-
Err(err) => Some(Err(err)),
972-
})
973-
.collect()
974-
}
975-
}
976-
977-
let visitor = SeqVisitor::<T, U> {
978-
marker: PhantomData,
979-
marker2: PhantomData,
980-
};
981-
deserializer.deserialize_seq(visitor)
982-
}
983-
}
984-
985911
impl<'de, Str> DeserializeAs<'de, Option<Str>> for NoneAsEmptyString
986912
where
987913
Str: FromStr,

serde_with/src/de/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#[cfg(feature = "alloc")]
1111
mod duplicates;
1212
mod impls;
13+
#[cfg(feature = "alloc")]
14+
mod skip_error;
1315

1416
use crate::prelude::*;
1517

serde_with/src/de/skip_error.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
use super::impls::macros::foreach_map;
2+
use crate::prelude::*;
3+
4+
#[cfg(feature = "hashbrown_0_14")]
5+
use hashbrown_0_14::HashMap as HashbrownMap014;
6+
#[cfg(feature = "indexmap_1")]
7+
use indexmap_1::IndexMap;
8+
#[cfg(feature = "indexmap_2")]
9+
use indexmap_2::IndexMap as IndexMap2;
10+
11+
enum GoodOrError<T, TAs> {
12+
Good(T),
13+
// Only here to consume the TAs generic
14+
Error(PhantomData<TAs>),
15+
}
16+
17+
impl<'de, T, TAs> Deserialize<'de> for GoodOrError<T, TAs>
18+
where
19+
TAs: DeserializeAs<'de, T>,
20+
{
21+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
22+
where
23+
D: Deserializer<'de>,
24+
{
25+
let is_hr = deserializer.is_human_readable();
26+
let content: content::de::Content<'de> = Deserialize::deserialize(deserializer)?;
27+
28+
Ok(
29+
match <DeserializeAsWrap<T, TAs>>::deserialize(content::de::ContentDeserializer::<
30+
D::Error,
31+
>::new(content, is_hr))
32+
{
33+
Ok(elem) => GoodOrError::Good(elem.into_inner()),
34+
Err(_) => GoodOrError::Error(PhantomData),
35+
},
36+
)
37+
}
38+
}
39+
40+
impl<'de, T, U> DeserializeAs<'de, Vec<T>> for VecSkipError<U>
41+
where
42+
U: DeserializeAs<'de, T>,
43+
{
44+
fn deserialize_as<D>(deserializer: D) -> Result<Vec<T>, D::Error>
45+
where
46+
D: Deserializer<'de>,
47+
{
48+
struct SeqVisitor<T, U> {
49+
marker: PhantomData<T>,
50+
marker2: PhantomData<U>,
51+
}
52+
53+
impl<'de, T, TAs> Visitor<'de> for SeqVisitor<T, TAs>
54+
where
55+
TAs: DeserializeAs<'de, T>,
56+
{
57+
type Value = Vec<T>;
58+
59+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
60+
formatter.write_str("a sequence")
61+
}
62+
63+
fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
64+
where
65+
A: SeqAccess<'de>,
66+
{
67+
utils::SeqIter::new(seq)
68+
.filter_map(|res: Result<GoodOrError<T, TAs>, A::Error>| match res {
69+
Ok(GoodOrError::Good(value)) => Some(Ok(value)),
70+
Ok(GoodOrError::Error(_)) => None,
71+
Err(err) => Some(Err(err)),
72+
})
73+
.collect()
74+
}
75+
}
76+
77+
let visitor = SeqVisitor::<T, U> {
78+
marker: PhantomData,
79+
marker2: PhantomData,
80+
};
81+
deserializer.deserialize_seq(visitor)
82+
}
83+
}
84+
85+
struct MapSkipErrorVisitor<MAP, K, KAs, V, VAs>(PhantomData<(MAP, K, KAs, V, VAs)>);
86+
87+
impl<'de, MAP, K, KAs, V, VAs> Visitor<'de> for MapSkipErrorVisitor<MAP, K, KAs, V, VAs>
88+
where
89+
MAP: FromIterator<(K, V)>,
90+
KAs: DeserializeAs<'de, K>,
91+
VAs: DeserializeAs<'de, V>,
92+
{
93+
type Value = MAP;
94+
95+
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96+
formatter.write_str("a map")
97+
}
98+
99+
#[inline]
100+
fn visit_map<A>(self, access: A) -> Result<Self::Value, A::Error>
101+
where
102+
A: MapAccess<'de>,
103+
{
104+
type KVPair<K, KAs, V, VAs> = (GoodOrError<K, KAs>, GoodOrError<V, VAs>);
105+
utils::MapIter::new(access)
106+
.filter_map(|res: Result<KVPair<K, KAs, V, VAs>, A::Error>| match res {
107+
Ok((GoodOrError::Good(key), GoodOrError::Good(value))) => Some(Ok((key, value))),
108+
Ok(_) => None,
109+
Err(err) => Some(Err(err)),
110+
})
111+
.collect()
112+
}
113+
}
114+
115+
#[cfg(feature = "alloc")]
116+
macro_rules! map_impl {
117+
(
118+
$ty:ident < K $(: $kbound1:ident $(+ $kbound2:ident)*)?, V $(, $typaram:ident : $bound1:ident $(+ $bound2:ident)*)* >,
119+
$with_capacity:expr
120+
) => {
121+
impl<'de, K, V, KAs, VAs $(, $typaram)*> DeserializeAs<'de, $ty<K, V $(, $typaram)*>>
122+
for MapSkipError<KAs, VAs>
123+
where
124+
KAs: DeserializeAs<'de, K>,
125+
VAs: DeserializeAs<'de, V>,
126+
$(K: $kbound1 $(+ $kbound2)*,)?
127+
$($typaram: $bound1 $(+ $bound2)*),*
128+
{
129+
fn deserialize_as<D>(deserializer: D) -> Result<$ty<K, V $(, $typaram)*>, D::Error>
130+
where
131+
D: Deserializer<'de>,
132+
{
133+
deserializer.deserialize_map(MapSkipErrorVisitor::<
134+
$ty<K, V $(, $typaram)*>,
135+
K,
136+
KAs,
137+
V,
138+
VAs,
139+
>(PhantomData))
140+
}
141+
}
142+
};
143+
}
144+
foreach_map!(map_impl);

serde_with/src/guide/serde_as_transformations.md

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,27 @@ This page lists the transformations implemented in this crate and supported by `
1111
7. [Convert to an intermediate type using `TryInto`](#convert-to-an-intermediate-type-using-tryinto)
1212
8. [`Default` from `null`](#default-from-null)
1313
9. [De/Serialize into `Vec`, ignoring errors](#deserialize-into-vec-ignoring-errors)
14-
10. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
15-
11. [`Duration` as seconds](#duration-as-seconds)
16-
12. [Hex encode bytes](#hex-encode-bytes)
17-
13. [Ignore deserialization errors](#ignore-deserialization-errors)
18-
14. [`Maps` to `Vec` of enums](#maps-to-vec-of-enums)
19-
15. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
20-
16. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
21-
17. [`None` as empty `String`](#none-as-empty-string)
22-
18. [One or many elements into `Vec`](#one-or-many-elements-into-vec)
23-
19. [Overwrite existing set values](#overwrite-existing-set-values)
24-
20. [Pick first successful deserialization](#pick-first-successful-deserialization)
25-
21. [Prefer the first map key when duplicates exist](#prefer-the-first-map-key-when-duplicates-exist)
26-
22. [Prevent duplicate map keys](#prevent-duplicate-map-keys)
27-
23. [Prevent duplicate set values](#prevent-duplicate-set-values)
28-
24. [Struct fields as map keys](#struct-fields-as-map-keys)
29-
25. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
30-
26. [Value into JSON String](#value-into-json-string)
31-
27. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
32-
28. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)
33-
29. [De/Serialize depending on `De/Serializer::is_human_readable`](#deserialize-depending-on-deserializeris_human_readable)
14+
10. [De/Serialize into a map, ignoring errors](#deserialize-into-a-map-ignoring-errors)
15+
11. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
16+
12. [`Duration` as seconds](#duration-as-seconds)
17+
13. [Hex encode bytes](#hex-encode-bytes)
18+
14. [Ignore deserialization errors](#ignore-deserialization-errors)
19+
15. [`Maps` to `Vec` of enums](#maps-to-vec-of-enums)
20+
16. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
21+
17. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
22+
18. [`None` as empty `String`](#none-as-empty-string)
23+
19. [One or many elements into `Vec`](#one-or-many-elements-into-vec)
24+
20. [Overwrite existing set values](#overwrite-existing-set-values)
25+
21. [Pick first successful deserialization](#pick-first-successful-deserialization)
26+
22. [Prefer the first map key when duplicates exist](#prefer-the-first-map-key-when-duplicates-exist)
27+
23. [Prevent duplicate map keys](#prevent-duplicate-map-keys)
28+
24. [Prevent duplicate set values](#prevent-duplicate-set-values)
29+
25. [Struct fields as map keys](#struct-fields-as-map-keys)
30+
26. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
31+
27. [Value into JSON String](#value-into-json-string)
32+
28. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
33+
29. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)
34+
30. [De/Serialize depending on `De/Serializer::is_human_readable`](#deserialize-depending-on-deserializeris_human_readable)
3435

3536
## Base64 encode bytes
3637

@@ -181,6 +182,25 @@ colors: Vec<Color>,
181182
// => vec![Blue, Green]
182183
```
183184

185+
## De/Serialize into a map, ignoring errors
186+
187+
[`MapSkipError`]
188+
189+
For formats with heterogeneously typed maps, we can collect only the elements where both key and value are deserializable.
190+
This is also useful in conjunction to `#[serde(flatten)]` to ignore some entries when capturing additional fields.
191+
192+
```ignore
193+
// JSON
194+
"value": {"0": "v0", "5": "v5", "str": "str", "10": 2},
195+
196+
// Rust
197+
#[serde_as(as = "MapSkipError<DisplayFromStr, _>")]
198+
value: BTreeMap<u32, String>,
199+
200+
// Only deserializes entries with a numerical key and a string value, i.e.,
201+
{0 => "v0", 5 => "v5"}
202+
```
203+
184204
## De/Serialize with `FromStr` and `Display`
185205

186206
Useful if a type implements `FromStr` / `Display` but not `Deserialize` / `Serialize`.
@@ -614,3 +634,4 @@ value: u128,
614634
[`TimestampSecondsWithFrac`]: crate::TimestampSecondsWithFrac
615635
[`TryFromInto`]: crate::TryFromInto
616636
[`VecSkipError`]: crate::VecSkipError
637+
[`MapSkipError`]: crate::MapSkipError

serde_with/src/lib.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2205,6 +2205,64 @@ pub struct BorrowCow;
22052205
#[cfg(feature = "alloc")]
22062206
pub struct VecSkipError<T>(PhantomData<T>);
22072207

2208+
/// Deserialize a map, skipping keys and values which fail to deserialize.
2209+
///
2210+
/// By default serde terminates if it fails to deserialize a key or a value when deserializing
2211+
/// a map. Sometimes a map has heterogeneous keys or values but we only care about some specific
2212+
/// types, and it is desirable to skip entries on errors.
2213+
///
2214+
/// It is especially useful in conjunction to `#[serde(flatten)]` to capture a map mixed in with
2215+
/// other entries which we don't want to exhaust in the type definition.
2216+
///
2217+
/// The serialization behavior is identical to the underlying map.
2218+
///
2219+
/// The implementation supports both the [`HashMap`] and the [`BTreeMap`] from the standard library.
2220+
///
2221+
/// [`BTreeMap`]: std::collections::BTreeMap
2222+
/// [`HashMap`]: std::collections::HashMap
2223+
///
2224+
/// # Examples
2225+
///
2226+
/// ```rust
2227+
/// # #[cfg(feature = "macros")] {
2228+
/// # use serde::{Deserialize, Serialize};
2229+
/// # use std::collections::BTreeMap;
2230+
/// # use serde_with::{serde_as, DisplayFromStr, MapSkipError};
2231+
/// #
2232+
/// #[serde_as]
2233+
/// # #[derive(Debug, PartialEq)]
2234+
/// #[derive(Deserialize, Serialize)]
2235+
/// struct VersionNames {
2236+
/// yanked: Vec<u16>,
2237+
/// #[serde_as(as = "MapSkipError<DisplayFromStr, _>")]
2238+
/// #[serde(flatten)]
2239+
/// names: BTreeMap<u16, String>,
2240+
/// }
2241+
///
2242+
/// let data = VersionNames {
2243+
/// yanked: vec![2, 5],
2244+
/// names: BTreeMap::from_iter([
2245+
/// (0u16, "v0".to_string()),
2246+
/// (1, "v1".to_string()),
2247+
/// (4, "v4".to_string())
2248+
/// ]),
2249+
/// };
2250+
/// let source_json = r#"{
2251+
/// "0": "v0",
2252+
/// "1": "v1",
2253+
/// "4": "v4",
2254+
/// "yanked": [2, 5],
2255+
/// "last_updated": 1704085200
2256+
/// }"#;
2257+
/// let data_json = r#"{"yanked":[2,5],"0":"v0","1":"v1","4":"v4"}"#;
2258+
/// // Ensure serialization and deserialization produce the expected results
2259+
/// assert_eq!(data_json, serde_json::to_string(&data).unwrap());
2260+
/// assert_eq!(data, serde_json::from_str(source_json).unwrap());
2261+
/// # }
2262+
/// ```
2263+
#[cfg(feature = "alloc")]
2264+
pub struct MapSkipError<K, V>(PhantomData<(K, V)>);
2265+
22082266
/// Deserialize a boolean from a number
22092267
///
22102268
/// Deserialize a number (of `u8`) and turn it into a boolean.

serde_with/src/ser/impls.rs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -559,19 +559,6 @@ where
559559
}
560560
}
561561

562-
#[cfg(feature = "alloc")]
563-
impl<T, U> SerializeAs<Vec<T>> for VecSkipError<U>
564-
where
565-
U: SerializeAs<T>,
566-
{
567-
fn serialize_as<S>(source: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
568-
where
569-
S: Serializer,
570-
{
571-
Vec::<U>::serialize_as(source, serializer)
572-
}
573-
}
574-
575562
impl<T> SerializeAs<Option<T>> for NoneAsEmptyString
576563
where
577564
T: Display,

0 commit comments

Comments
 (0)