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

Implement MapSkipError, analogous to VecSkipError, but for map-like data structures. #763

Merged
merged 4 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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