Skip to content

Commit

Permalink
Merge #563
Browse files Browse the repository at this point in the history
563: Add serde_as compatible versions for duplicate key handling r=jonasbb a=jonasbb

This adds `serde_as` compatible versions for these four modules:
`maps_duplicate_key_is_error`, `maps_first_key_wins`,
`sets_duplicate_value_is_error`, and `sets_last_value_wins`.

Missing:
- [x] Documentation

Closes #534

Co-authored-by: Jonas Bushart <jonas@bushart.org>
  • Loading branch information
bors[bot] and jonasbb committed Mar 4, 2023
2 parents ed04314 + ad49c0c commit b50b611
Show file tree
Hide file tree
Showing 8 changed files with 523 additions and 6 deletions.
5 changes: 5 additions & 0 deletions serde_with/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

* Add `serde_as` compatible versions for the existing duplicate key and value handling. (#534)
The new types `MapPreventDuplicates`, `MapFirstKeyWins`, `SetPreventDuplicates`, and `SetLastValueWins` can replace the existing modules `maps_duplicate_key_is_error`, `maps_first_key_wins`, `sets_duplicate_value_is_error`, and `sets_last_value_wins`.

### Fixed

* `EnumMap` passes the `human_readable` status of the `Serializer` to more places.
Expand Down
223 changes: 223 additions & 0 deletions serde_with/src/de/duplicates.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
use super::impls::{foreach_map_create, foreach_set_create};
use crate::{
duplicate_key_impls::{
DuplicateInsertsFirstWinsMap, DuplicateInsertsLastWinsSet, PreventDuplicateInsertsMap,
PreventDuplicateInsertsSet,
},
prelude::*,
MapFirstKeyWins, MapPreventDuplicates, SetLastValueWins, SetPreventDuplicates,
};
#[cfg(feature = "indexmap_1")]
use indexmap_1::{IndexMap, IndexSet};

struct SetPreventDuplicatesVisitor<SET, T, TAs>(PhantomData<(SET, T, TAs)>);

impl<'de, SET, T, TAs> Visitor<'de> for SetPreventDuplicatesVisitor<SET, T, TAs>
where
SET: PreventDuplicateInsertsSet<T>,
TAs: DeserializeAs<'de, T>,
{
type Value = SET;

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

#[inline]
fn visit_seq<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut values = Self::Value::new(access.size_hint());

while let Some(value) = access.next_element::<DeserializeAsWrap<T, TAs>>()? {
if !values.insert(value.into_inner()) {
return Err(DeError::custom("invalid entry: found duplicate value"));
};
}

Ok(values)
}
}

struct SetLastValueWinsVisitor<SET, T, TAs>(PhantomData<(SET, T, TAs)>);

impl<'de, SET, T, TAs> Visitor<'de> for SetLastValueWinsVisitor<SET, T, TAs>
where
SET: DuplicateInsertsLastWinsSet<T>,
TAs: DeserializeAs<'de, T>,
{
type Value = SET;

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

#[inline]
fn visit_seq<A>(self, mut access: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut values = Self::Value::new(access.size_hint());

while let Some(value) = access.next_element::<DeserializeAsWrap<T, TAs>>()? {
values.replace(value.into_inner());
}

Ok(values)
}
}

#[cfg(feature = "alloc")]
macro_rules! set_impl {
(
$ty:ident < T $(: $tbound1:ident $(+ $tbound2:ident)*)* $(, $typaram:ident : $bound1:ident $(+ $bound2:ident)* )* >,
$with_capacity:expr,
$append:ident
) => {
impl<'de, T, TAs $(, $typaram)*> DeserializeAs<'de, $ty<T $(, $typaram)*>> for SetPreventDuplicates<TAs>
where
TAs: DeserializeAs<'de, T>,
$(T: $tbound1 $(+ $tbound2)*,)*
$($typaram: $bound1 $(+ $bound2)*),*
{
fn deserialize_as<D>(deserializer: D) -> Result<$ty<T $(, $typaram)*>, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(SetPreventDuplicatesVisitor::<$ty<T $(, $typaram)*>, T, TAs>(
PhantomData,
))
}
}

impl<'de, T, TAs $(, $typaram)*> DeserializeAs<'de, $ty<T $(, $typaram)*>> for SetLastValueWins<TAs>
where
TAs: DeserializeAs<'de, T>,
$(T: $tbound1 $(+ $tbound2)*,)*
$($typaram: $bound1 $(+ $bound2)*),*
{
fn deserialize_as<D>(deserializer: D) -> Result<$ty<T $(, $typaram)*>, D::Error>
where
D: Deserializer<'de>,
{
deserializer
.deserialize_seq(SetLastValueWinsVisitor::<$ty<T $(, $typaram)*>, T, TAs>(PhantomData))
}
}
}
}
foreach_set_create!(set_impl);

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

impl<'de, MAP, K, KAs, V, VAs> Visitor<'de> for MapPreventDuplicatesVisitor<MAP, K, KAs, V, VAs>
where
MAP: PreventDuplicateInsertsMap<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, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut values = Self::Value::new(access.size_hint());

while let Some((key, value)) =
access.next_entry::<DeserializeAsWrap<K, KAs>, DeserializeAsWrap<V, VAs>>()?
{
if !values.insert(key.into_inner(), value.into_inner()) {
return Err(DeError::custom("invalid entry: found duplicate key"));
};
}

Ok(values)
}
}

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

impl<'de, MAP, K, KAs, V, VAs> Visitor<'de> for MapFirstKeyWinsVisitor<MAP, K, KAs, V, VAs>
where
MAP: DuplicateInsertsFirstWinsMap<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, mut access: A) -> Result<Self::Value, A::Error>
where
A: MapAccess<'de>,
{
let mut values = Self::Value::new(access.size_hint());

while let Some((key, value)) =
access.next_entry::<DeserializeAsWrap<K, KAs>, DeserializeAsWrap<V, VAs>>()?
{
values.insert(key.into_inner(), value.into_inner());
}

Ok(values)
}
}

#[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 MapPreventDuplicates<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(MapPreventDuplicatesVisitor::<
$ty<K, V $(, $typaram)*>,
K,
KAs,
V,
VAs,
>(PhantomData))
}
}

impl<'de, K, V, KAs, VAs $(, $typaram)*> DeserializeAs<'de, $ty<K, V $(, $typaram)*>>
for MapFirstKeyWins<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(MapFirstKeyWinsVisitor::<$ty<K, V $(, $typaram)*>, K, KAs, V, VAs>(
PhantomData,
))
}
}
};
}
foreach_map_create!(map_impl);
2 changes: 2 additions & 0 deletions serde_with/src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
//!
//! [user guide]: crate::guide

#[cfg(feature = "alloc")]
mod duplicates;
mod impls;

use crate::prelude::*;
Expand Down
44 changes: 39 additions & 5 deletions serde_with/src/guide/serde_as_transformations.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ This page lists the transformations implemented in this crate and supported by `
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. [Pick first successful deserialization](#pick-first-successful-deserialization)
20. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
21. [Value into JSON String](#value-into-json-string)
22. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
23. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)
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. [Timestamps as seconds since UNIX epoch](#timestamps-as-seconds-since-unix-epoch)
25. [Value into JSON String](#value-into-json-string)
26. [`Vec` of tuples to `Maps`](#vec-of-tuples-to-maps)
27. [Well-known time formats for `OffsetDateTime`](#well-known-time-formats-for-offsetdatetime)

## Base64 encode bytes

Expand Down Expand Up @@ -371,6 +375,13 @@ value: Vec<String>,
"value": ["Hello", "World!"], // or lists of many
```

## Overwrite existing set values

[`SetLastValueWins`]

serdes default behavior for sets is to take the first value, when multiple "equal" values are inserted into a set.
This changes the logic, to prefer the last value.

## Pick first successful deserialization

[`PickFirst`]
Expand All @@ -388,6 +399,25 @@ value: u32,
"value": "666",
```

## Prefer the first map key when duplicates exist

[`MapFirstKeyWins`]

serdes default behavior is to take the last key and value combination, if multiple "equal" keys exist.
This changes the logic to instead prefer the first found key-value combination.

## Prevent duplicate map keys

[`MapPreventDuplicates`]

Error during deserialization, when duplicate map keys are detected.

## Prevent duplicate set values

[`SetPreventDuplicates`]

Error during deserialization, when duplicate set values are detected.

## Timestamps as seconds since UNIX epoch

[`TimestampSeconds`]
Expand Down Expand Up @@ -515,9 +545,13 @@ These conversions are available with the `time_0_3` feature flag.
[`FromInto`]: crate::FromInto
[`Hex`]: crate::hex::Hex
[`JsonString`]: crate::json::JsonString
[`MapFirstKeyWins`]: crate::MapFirstKeyWins
[`MapPreventDuplicates`]: crate::MapPreventDuplicates
[`NoneAsEmptyString`]: crate::NoneAsEmptyString
[`OneOrMany`]: crate::OneOrMany
[`PickFirst`]: crate::PickFirst
[`SetLastValueWins`]: crate::SetLastValueWins
[`SetPreventDuplicates`]: crate::SetPreventDuplicates
[`time::Duration`]: time_0_3::Duration
[`time::format_description::well_known::Iso8601`]: time_0_3::format_description::well_known::Iso8601
[`time::format_description::well_known::Rfc2822`]: time_0_3::format_description::well_known::Rfc2822
Expand Down

0 comments on commit b50b611

Please sign in to comment.