Skip to content

Commit e58a162

Browse files
Implement OneOrMany for std::collections::HashSet
1 parent 4031878 commit e58a162

File tree

4 files changed

+135
-8
lines changed

4 files changed

+135
-8
lines changed

serde_with/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [Unreleased]
99

10+
### Added
11+
12+
* Support `OneOrMany` with `HashSet`
13+
1014
## [3.17.0] - 2026-02-24
1115

1216
### Added

serde_with/src/de/impls.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,45 @@ where
17711771
}
17721772
}
17731773

1774+
#[cfg(all(feature = "alloc", feature = "std"))]
1775+
impl<'de, T, TAs, FORMAT, S> DeserializeAs<'de, std::collections::HashSet<T, S>>
1776+
for OneOrMany<TAs, FORMAT>
1777+
where
1778+
T: Eq + std::hash::Hash,
1779+
S: BuildHasher + Default,
1780+
TAs: DeserializeAs<'de, T>,
1781+
FORMAT: Format,
1782+
{
1783+
fn deserialize_as<D>(deserializer: D) -> Result<std::collections::HashSet<T, S>, D::Error>
1784+
where
1785+
D: Deserializer<'de>,
1786+
{
1787+
let is_hr = deserializer.is_human_readable();
1788+
let content: content::de::Content<'de> = Deserialize::deserialize(deserializer)?;
1789+
1790+
let one_err: D::Error = match <DeserializeAsWrap<T, TAs>>::deserialize(
1791+
content::de::ContentRefDeserializer::new(&content, is_hr),
1792+
) {
1793+
Ok(one) => {
1794+
let mut hashset = std::collections::HashSet::default();
1795+
hashset.insert(one.into_inner());
1796+
return Ok(hashset);
1797+
}
1798+
Err(err) => err,
1799+
};
1800+
let many_err: D::Error =
1801+
match <DeserializeAsWrap<HashSet<T, S>, HashSet<TAs, S>>>::deserialize(
1802+
content::de::ContentDeserializer::new(content, is_hr),
1803+
) {
1804+
Ok(many) => return Ok(many.into_inner()),
1805+
Err(err) => err,
1806+
};
1807+
Err(DeError::custom(format_args!(
1808+
"OneOrMany could not deserialize any variant:\n One: {one_err}\n Many: {many_err}"
1809+
)))
1810+
}
1811+
}
1812+
17741813
#[cfg(feature = "alloc")]
17751814
impl<'de, T, TAs1> DeserializeAs<'de, T> for PickFirst<(TAs1,)>
17761815
where

serde_with/src/ser/impls.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,7 @@ where
945945
S: Serializer,
946946
{
947947
match source.len() {
948-
1 => SerializeAsWrap::<T, U>::new(source.iter().next().expect("Cannot be empty"))
948+
1 => SerializeAsWrap::<T, U>::new(source.first().expect("Cannot be empty"))
949949
.serialize(serializer),
950950
_ => SerializeAsWrap::<Vec<T>, Vec<U>>::new(source).serialize(serializer),
951951
}
@@ -976,7 +976,7 @@ where
976976
S: Serializer,
977977
{
978978
match source.len() {
979-
1 => SerializeAsWrap::<T, TAs>::new(source.iter().next().expect("Cannot be empty"))
979+
1 => SerializeAsWrap::<T, TAs>::new(source.first().expect("Cannot be empty"))
980980
.serialize(serializer),
981981
_ => SerializeAsWrap::<&[T], &[TAs]>::new(&source.as_slice()).serialize(serializer),
982982
}
@@ -997,6 +997,46 @@ where
997997
}
998998
}
999999

1000+
#[cfg(all(feature = "alloc", feature = "std"))]
1001+
impl<T, U, S> SerializeAs<std::collections::HashSet<T, S>> for OneOrMany<U, formats::PreferOne>
1002+
where
1003+
T: Clone + Eq + core::hash::Hash,
1004+
U: SerializeAs<T>,
1005+
{
1006+
fn serialize_as<Ser>(
1007+
source: &std::collections::HashSet<T, S>,
1008+
serializer: Ser,
1009+
) -> Result<Ser::Ok, Ser::Error>
1010+
where
1011+
Ser: Serializer,
1012+
{
1013+
match source.len() {
1014+
1 => SerializeAsWrap::<T, U>::new(source.iter().next().expect("Cannot be empty")).serialize(serializer),
1015+
_ => SerializeAsWrap::<std::collections::HashSet<T, S>, std::collections::HashSet<U, S>>::new(source).serialize(serializer),
1016+
}
1017+
}
1018+
}
1019+
1020+
#[cfg(all(feature = "alloc", feature = "std"))]
1021+
impl<T, U, S> SerializeAs<std::collections::HashSet<T, S>> for OneOrMany<U, formats::PreferMany>
1022+
where
1023+
T: Eq + core::hash::Hash,
1024+
U: SerializeAs<T>,
1025+
{
1026+
fn serialize_as<Ser>(
1027+
source: &std::collections::HashSet<T, S>,
1028+
serializer: Ser,
1029+
) -> Result<Ser::Ok, Ser::Error>
1030+
where
1031+
Ser: Serializer,
1032+
{
1033+
SerializeAsWrap::<std::collections::HashSet<T, S>, std::collections::HashSet<U, S>>::new(
1034+
source,
1035+
)
1036+
.serialize(serializer)
1037+
}
1038+
}
1039+
10001040
#[cfg(feature = "alloc")]
10011041
impl<T, TAs1> SerializeAs<T> for PickFirst<(TAs1,)>
10021042
where

serde_with/tests/serde_as/lib.rs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ use serde_with::{
3535
NoneAsEmptyString, OneOrMany, Same, Seq, StringWithSeparator,
3636
};
3737
use std::{
38-
collections::HashMap,
38+
collections::{HashMap, HashSet},
3939
sync::{Mutex, RwLock},
4040
};
4141

@@ -1183,11 +1183,11 @@ fn test_one_or_many_prefer_one() {
11831183
is_equal(
11841184
S1Vec(vec![1, 2, 3]),
11851185
expect![[r#"
1186-
[
1187-
1,
1188-
2,
1189-
3
1190-
]"#]],
1186+
[
1187+
1,
1188+
2,
1189+
3
1190+
]"#]],
11911191
);
11921192
check_deserialization(S1Vec(vec![1]), r#"1"#);
11931193
check_deserialization(S1Vec(vec![1]), r#"[1]"#);
@@ -1323,6 +1323,50 @@ fn test_one_or_many_prefer_many() {
13231323
);
13241324
}
13251325

1326+
#[test]
1327+
fn test_one_or_many_hashset_prefer_one() {
1328+
#[serde_as]
1329+
#[derive(Debug, Serialize, Deserialize, PartialEq)]
1330+
struct S1(#[serde_as(as = "OneOrMany<_>")] HashSet<u32>);
1331+
1332+
is_equal(S1(HashSet::new()), expect![[r#"[]"#]]);
1333+
is_equal(S1(HashSet::from([1])), expect![[r#"1"#]]);
1334+
check_deserialization(S1(HashSet::from([1])), r#"1"#);
1335+
check_deserialization(S1(HashSet::from([1])), r#"[1]"#);
1336+
check_deserialization(S1(HashSet::from([1, 2, 3])), r#"[1, 2, 3]"#);
1337+
check_deserialization(S1(HashSet::from([1, 2, 3])), r#"[3, 2, 1]"#);
1338+
1339+
#[serde_as]
1340+
#[derive(Debug, Serialize, Deserialize, PartialEq)]
1341+
struct S2(#[serde_as(as = "OneOrMany<DisplayFromStr>")] HashSet<u32>);
1342+
1343+
is_equal(S2(HashSet::from([1])), expect![[r#""1""#]]);
1344+
check_deserialization(S2(HashSet::from([1])), r#""1""#);
1345+
check_deserialization(S2(HashSet::from([1])), r#"["1"]"#);
1346+
check_deserialization(S2(HashSet::from([1, 2, 3])), r#"["1", "2", "3"]"#);
1347+
}
1348+
1349+
#[test]
1350+
fn test_one_or_many_hashset_prefer_many() {
1351+
use serde_with::formats::PreferMany;
1352+
1353+
#[serde_as]
1354+
#[derive(Debug, Serialize, Deserialize, PartialEq)]
1355+
struct S1(#[serde_as(as = "OneOrMany<_, PreferMany>")] HashSet<u32>);
1356+
1357+
is_equal(S1(HashSet::new()), expect![[r#"[]"#]]);
1358+
is_equal(
1359+
S1(HashSet::from([1])),
1360+
expect![[r#"
1361+
[
1362+
1
1363+
]"#]],
1364+
);
1365+
check_deserialization(S1(HashSet::from([1])), r#"1"#);
1366+
check_deserialization(S1(HashSet::from([1])), r#"[1]"#);
1367+
check_deserialization(S1(HashSet::from([1, 2, 3])), r#"[1, 2, 3]"#);
1368+
}
1369+
13261370
/// Test that Cow borrows from the input
13271371
#[test]
13281372
fn test_borrow_cow_str() {

0 commit comments

Comments
 (0)