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

[pull] master from jonasbb:master #68

Merged
merged 27 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
83b3875
Bump time to v0.3.36 to include nightly fix
jonasbb May 22, 2024
55db6fc
Bump MSRV for time dependency
jonasbb May 22, 2024
c348ea4
Bump time to 0.3.36 for nightly compatability (#750)
jonasbb May 22, 2024
242286c
Enable the `unexpected_cfgs` lint now that it can be configured via `…
jonasbb May 22, 2024
9593f93
Enable the `unexpected_cfgs` lint now that it can be configured via `…
jonasbb May 22, 2024
685338f
Replace future deprecated functions from chrono
jonasbb May 22, 2024
729e8d2
Replace future deprecated functions from chrono (#752)
jonasbb May 22, 2024
3ae4424
Fix two clippy issues
jonasbb Jun 8, 2024
f74b460
Fix two clippy issues (#755)
jonasbb Jun 9, 2024
dee706a
Implement JsonSchemaAs for OneOrMany instead of JsonSchema
swlynch99 Jun 28, 2024
c9d9672
Implement JsonSchemaAs for OneOrMany instead of JsonSchema (#760)
jonasbb Jun 30, 2024
e9e7a7e
Bump version to 3.8.2
jonasbb Jun 30, 2024
2274dd1
Bump version to 3.8.2 (#761)
jonasbb Jun 30, 2024
4c8c2db
Make code compile with `schemars_0_8/preserve_order` enabled
jonasbb Jul 3, 2024
19bfe18
Make code compile with `schemars_0_8/preserve_order` enabled (#764)
jonasbb Jul 3, 2024
7de9838
Bump version to v3.8.3
jonasbb Jul 3, 2024
1c4b022
Bump version to v3.8.3 (#765)
jonasbb Jul 3, 2024
4ad4a1b
Fix dead code warning by correcting a typo
jonasbb Jul 12, 2024
f5b2626
Fix dead code warnings in tests
jonasbb Jul 12, 2024
31e9172
Fix dead code warning by correcting a typo (#769)
jonasbb Jul 12, 2024
bf6724d
Move VecSkipError to a separate file, preparing for MapSkipError
johnmave126 Jul 3, 2024
97543d0
Implement MapSkipError, skipping un-deserializable entries.
johnmave126 Jul 3, 2024
17dec11
Add tests to make sure syntax errors are not suppressed.
johnmave126 Jul 4, 2024
aaa0a29
Update serde_with/src/guide/serde_as_transformations.md
johnmave126 Jul 12, 2024
d038657
Implement `MapSkipError`, analogous to `VecSkipError`, but for map-li…
jonasbb Jul 12, 2024
57ad877
Bump version to 3.9.0
jonasbb Jul 14, 2024
c3e489f
Bump version to 3.9.0 (#770)
jonasbb Jul 14, 2024
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