Skip to content

Commit

Permalink
Implement MapSkipError, analogous to VecSkipError, but for map-li…
Browse files Browse the repository at this point in the history
…ke data structures. (#763)
  • Loading branch information
jonasbb committed Jul 12, 2024
2 parents 31e9172 + aaa0a29 commit d038657
Show file tree
Hide file tree
Showing 12 changed files with 752 additions and 107 deletions.
74 changes: 0 additions & 74 deletions serde_with/src/de/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -908,80 +908,6 @@ where
}
}

#[cfg(feature = "alloc")]
impl<'de, T, U> DeserializeAs<'de, Vec<T>> for VecSkipError<U>
where
U: DeserializeAs<'de, T>,
{
fn deserialize_as<D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
{
enum GoodOrError<T, TAs> {
Good(T),
// Only here to consume the TAs generic
Error(PhantomData<TAs>),
}

impl<'de, T, TAs> Deserialize<'de> for GoodOrError<T, TAs>
where
TAs: DeserializeAs<'de, T>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let is_hr = deserializer.is_human_readable();
let content: content::de::Content<'de> = Deserialize::deserialize(deserializer)?;

Ok(
match <DeserializeAsWrap<T, TAs>>::deserialize(
content::de::ContentDeserializer::<D::Error>::new(content, is_hr),
) {
Ok(elem) => GoodOrError::Good(elem.into_inner()),
Err(_) => GoodOrError::Error(PhantomData),
},
)
}
}

struct SeqVisitor<T, U> {
marker: PhantomData<T>,
marker2: PhantomData<U>,
}

impl<'de, T, TAs> Visitor<'de> for SeqVisitor<T, TAs>
where
TAs: DeserializeAs<'de, T>,
{
type Value = Vec<T>;

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

fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
utils::SeqIter::new(seq)
.filter_map(|res: Result<GoodOrError<T, TAs>, A::Error>| match res {
Ok(GoodOrError::Good(value)) => Some(Ok(value)),
Ok(GoodOrError::Error(_)) => None,
Err(err) => Some(Err(err)),
})
.collect()
}
}

let visitor = SeqVisitor::<T, U> {
marker: PhantomData,
marker2: PhantomData,
};
deserializer.deserialize_seq(visitor)
}
}

impl<'de, Str> DeserializeAs<'de, Option<Str>> for NoneAsEmptyString
where
Str: FromStr,
Expand Down
2 changes: 2 additions & 0 deletions serde_with/src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#[cfg(feature = "alloc")]
mod duplicates;
mod impls;
#[cfg(feature = "alloc")]
mod skip_error;

use crate::prelude::*;

Expand Down
144 changes: 144 additions & 0 deletions serde_with/src/de/skip_error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
use super::impls::macros::foreach_map;
use crate::prelude::*;

#[cfg(feature = "hashbrown_0_14")]
use hashbrown_0_14::HashMap as HashbrownMap014;
#[cfg(feature = "indexmap_1")]
use indexmap_1::IndexMap;
#[cfg(feature = "indexmap_2")]
use indexmap_2::IndexMap as IndexMap2;

enum GoodOrError<T, TAs> {
Good(T),
// Only here to consume the TAs generic
Error(PhantomData<TAs>),
}

impl<'de, T, TAs> Deserialize<'de> for GoodOrError<T, TAs>
where
TAs: DeserializeAs<'de, T>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let is_hr = deserializer.is_human_readable();
let content: content::de::Content<'de> = Deserialize::deserialize(deserializer)?;

Ok(
match <DeserializeAsWrap<T, TAs>>::deserialize(content::de::ContentDeserializer::<
D::Error,
>::new(content, is_hr))
{
Ok(elem) => GoodOrError::Good(elem.into_inner()),
Err(_) => GoodOrError::Error(PhantomData),
},
)
}
}

impl<'de, T, U> DeserializeAs<'de, Vec<T>> for VecSkipError<U>
where
U: DeserializeAs<'de, T>,
{
fn deserialize_as<D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
{
struct SeqVisitor<T, U> {
marker: PhantomData<T>,
marker2: PhantomData<U>,
}

impl<'de, T, TAs> Visitor<'de> for SeqVisitor<T, TAs>
where
TAs: DeserializeAs<'de, T>,
{
type Value = Vec<T>;

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

fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
utils::SeqIter::new(seq)
.filter_map(|res: Result<GoodOrError<T, TAs>, A::Error>| match res {
Ok(GoodOrError::Good(value)) => Some(Ok(value)),
Ok(GoodOrError::Error(_)) => None,
Err(err) => Some(Err(err)),
})
.collect()
}
}

let visitor = SeqVisitor::<T, U> {
marker: PhantomData,
marker2: PhantomData,
};
deserializer.deserialize_seq(visitor)
}
}

struct MapSkipErrorVisitor<MAP, K, KAs, V, VAs>(PhantomData<(MAP, K, KAs, V, VAs)>);

impl<'de, MAP, K, KAs, V, VAs> Visitor<'de> for MapSkipErrorVisitor<MAP, K, KAs, V, VAs>
where
MAP: FromIterator<(K, V)>,
KAs: DeserializeAs<'de, K>,
VAs: DeserializeAs<'de, V>,
{
type Value = MAP;

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

#[inline]
fn visit_map<A>(self, access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
type KVPair<K, KAs, V, VAs> = (GoodOrError<K, KAs>, GoodOrError<V, VAs>);
utils::MapIter::new(access)
.filter_map(|res: Result<KVPair<K, KAs, V, VAs>, A::Error>| match res {
Ok((GoodOrError::Good(key), GoodOrError::Good(value))) => Some(Ok((key, value))),
Ok(_) => None,
Err(err) => Some(Err(err)),
})
.collect()
}
}

#[cfg(feature = "alloc")]
macro_rules! map_impl {
(
$ty:ident < K $(: $kbound1:ident $(+ $kbound2:ident)*)?, V $(, $typaram:ident : $bound1:ident $(+ $bound2:ident)*)* >,
$with_capacity:expr
) => {
impl<'de, K, V, KAs, VAs $(, $typaram)*> DeserializeAs<'de, $ty<K, V $(, $typaram)*>>
for MapSkipError<KAs, VAs>
where
KAs: DeserializeAs<'de, K>,
VAs: DeserializeAs<'de, V>,
$(K: $kbound1 $(+ $kbound2)*,)?
$($typaram: $bound1 $(+ $bound2)*),*
{
fn deserialize_as<D>(deserializer: D) -> Result<$ty<K, V $(, $typaram)*>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(MapSkipErrorVisitor::<
$ty<K, V $(, $typaram)*>,
K,
KAs,
V,
VAs,
>(PhantomData))
}
}
};
}
foreach_map!(map_impl);
61 changes: 41 additions & 20 deletions serde_with/src/guide/serde_as_transformations.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,27 @@ This page lists the transformations implemented in this crate and supported by `
7. [Convert to an intermediate type using `TryInto`](#convert-to-an-intermediate-type-using-tryinto)
8. [`Default` from `null`](#default-from-null)
9. [De/Serialize into `Vec`, ignoring errors](#deserialize-into-vec-ignoring-errors)
10. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
11. [`Duration` as seconds](#duration-as-seconds)
12. [Hex encode bytes](#hex-encode-bytes)
13. [Ignore deserialization errors](#ignore-deserialization-errors)
14. [`Maps` to `Vec` of enums](#maps-to-vec-of-enums)
15. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
16. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
17. [`None` as empty `String`](#none-as-empty-string)
18. [One or many elements into `Vec`](#one-or-many-elements-into-vec)
19. [Overwrite existing set values](#overwrite-existing-set-values)
20. [Pick first successful deserialization](#pick-first-successful-deserialization)
21. [Prefer the first map key when duplicates exist](#prefer-the-first-map-key-when-duplicates-exist)
22. [Prevent duplicate map keys](#prevent-duplicate-map-keys)
23. [Prevent duplicate set values](#prevent-duplicate-set-values)
24. [Struct fields as map keys](#struct-fields-as-map-keys)
25. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
26. [Value into JSON String](#value-into-json-string)
27. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
28. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)
29. [De/Serialize depending on `De/Serializer::is_human_readable`](#deserialize-depending-on-deserializeris_human_readable)
10. [De/Serialize into a map, ignoring errors](#deserialize-into-a-map-ignoring-errors)
11. [De/Serialize with `FromStr` and `Display`](#deserialize-with-fromstr-and-display)
12. [`Duration` as seconds](#duration-as-seconds)
13. [Hex encode bytes](#hex-encode-bytes)
14. [Ignore deserialization errors](#ignore-deserialization-errors)
15. [`Maps` to `Vec` of enums](#maps-to-vec-of-enums)
16. [`Maps` to `Vec` of tuples](#maps-to-vec-of-tuples)
17. [`NaiveDateTime` like UTC timestamp](#naivedatetime-like-utc-timestamp)
18. [`None` as empty `String`](#none-as-empty-string)
19. [One or many elements into `Vec`](#one-or-many-elements-into-vec)
20. [Overwrite existing set values](#overwrite-existing-set-values)
21. [Pick first successful deserialization](#pick-first-successful-deserialization)
22. [Prefer the first map key when duplicates exist](#prefer-the-first-map-key-when-duplicates-exist)
23. [Prevent duplicate map keys](#prevent-duplicate-map-keys)
24. [Prevent duplicate set values](#prevent-duplicate-set-values)
25. [Struct fields as map keys](#struct-fields-as-map-keys)
26. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
27. [Value into JSON String](#value-into-json-string)
28. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
29. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)
30. [De/Serialize depending on `De/Serializer::is_human_readable`](#deserialize-depending-on-deserializeris_human_readable)

## Base64 encode bytes

Expand Down Expand Up @@ -181,6 +182,25 @@ colors: Vec<Color>,
// => vec![Blue, Green]
```

## De/Serialize into a map, ignoring errors

[`MapSkipError`]

For formats with heterogeneously typed maps, we can collect only the elements where both key and value are deserializable.
This is also useful in conjunction to `#[serde(flatten)]` to ignore some entries when capturing additional fields.

```ignore
// JSON
"value": {"0": "v0", "5": "v5", "str": "str", "10": 2},
// Rust
#[serde_as(as = "MapSkipError<DisplayFromStr, _>")]
value: BTreeMap<u32, String>,
// Only deserializes entries with a numerical key and a string value, i.e.,
{0 => "v0", 5 => "v5"}
```

## De/Serialize with `FromStr` and `Display`

Useful if a type implements `FromStr` / `Display` but not `Deserialize` / `Serialize`.
Expand Down Expand Up @@ -614,3 +634,4 @@ value: u128,
[`TimestampSecondsWithFrac`]: crate::TimestampSecondsWithFrac
[`TryFromInto`]: crate::TryFromInto
[`VecSkipError`]: crate::VecSkipError
[`MapSkipError`]: crate::MapSkipError
58 changes: 58 additions & 0 deletions serde_with/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2205,6 +2205,64 @@ pub struct BorrowCow;
#[cfg(feature = "alloc")]
pub struct VecSkipError<T>(PhantomData<T>);

/// Deserialize a map, skipping keys and values which fail to deserialize.
///
/// By default serde terminates if it fails to deserialize a key or a value when deserializing
/// a map. Sometimes a map has heterogeneous keys or values but we only care about some specific
/// types, and it is desirable to skip entries on errors.
///
/// It is especially useful in conjunction to `#[serde(flatten)]` to capture a map mixed in with
/// other entries which we don't want to exhaust in the type definition.
///
/// The serialization behavior is identical to the underlying map.
///
/// The implementation supports both the [`HashMap`] and the [`BTreeMap`] from the standard library.
///
/// [`BTreeMap`]: std::collections::BTreeMap
/// [`HashMap`]: std::collections::HashMap
///
/// # Examples
///
/// ```rust
/// # #[cfg(feature = "macros")] {
/// # use serde::{Deserialize, Serialize};
/// # use std::collections::BTreeMap;
/// # use serde_with::{serde_as, DisplayFromStr, MapSkipError};
/// #
/// #[serde_as]
/// # #[derive(Debug, PartialEq)]
/// #[derive(Deserialize, Serialize)]
/// struct VersionNames {
/// yanked: Vec<u16>,
/// #[serde_as(as = "MapSkipError<DisplayFromStr, _>")]
/// #[serde(flatten)]
/// names: BTreeMap<u16, String>,
/// }
///
/// let data = VersionNames {
/// yanked: vec![2, 5],
/// names: BTreeMap::from_iter([
/// (0u16, "v0".to_string()),
/// (1, "v1".to_string()),
/// (4, "v4".to_string())
/// ]),
/// };
/// let source_json = r#"{
/// "0": "v0",
/// "1": "v1",
/// "4": "v4",
/// "yanked": [2, 5],
/// "last_updated": 1704085200
/// }"#;
/// let data_json = r#"{"yanked":[2,5],"0":"v0","1":"v1","4":"v4"}"#;
/// // Ensure serialization and deserialization produce the expected results
/// assert_eq!(data_json, serde_json::to_string(&data).unwrap());
/// assert_eq!(data, serde_json::from_str(source_json).unwrap());
/// # }
/// ```
#[cfg(feature = "alloc")]
pub struct MapSkipError<K, V>(PhantomData<(K, V)>);

/// Deserialize a boolean from a number
///
/// Deserialize a number (of `u8`) and turn it into a boolean.
Expand Down
13 changes: 0 additions & 13 deletions serde_with/src/ser/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,19 +559,6 @@ where
}
}

#[cfg(feature = "alloc")]
impl<T, U> SerializeAs<Vec<T>> for VecSkipError<U>
where
U: SerializeAs<T>,
{
fn serialize_as<S>(source: &Vec<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Vec::<U>::serialize_as(source, serializer)
}
}

impl<T> SerializeAs<Option<T>> for NoneAsEmptyString
where
T: Display,
Expand Down
Loading

0 comments on commit d038657

Please sign in to comment.