iddqd/bi_hash_map/serde_impls.rs
1use crate::{
2 BiHashItem, BiHashMap, DefaultHashBuilder,
3 support::{
4 alloc::{Allocator, Global},
5 size_hint::cautious,
6 },
7};
8use core::{fmt, hash::BuildHasher, marker::PhantomData};
9use serde_core::{
10 Deserialize, Deserializer, Serialize, Serializer,
11 de::{MapAccess, SeqAccess, Visitor},
12 ser::SerializeMap,
13};
14
15/// A `BiHashMap` serializes to the list of items. Items are serialized in
16/// arbitrary order.
17///
18/// Serializing as a list of items rather than as a map works around the lack of
19/// non-string keys in formats like JSON.
20///
21/// To serialize as a map instead, see [`BiHashMapAsMap`].
22///
23/// # Examples
24///
25/// ```
26/// # #[cfg(feature = "default-hasher")] {
27/// use iddqd::{BiHashItem, BiHashMap, bi_upcast};
28/// # use iddqd_test_utils::serde_json;
29/// use serde::{Deserialize, Serialize};
30///
31/// #[derive(Debug, Serialize)]
32/// struct Item {
33/// id: u32,
34/// name: String,
35/// email: String,
36/// value: usize,
37/// }
38///
39/// // This is a complex key, so it can't be a JSON map key.
40/// #[derive(Eq, Hash, PartialEq)]
41/// struct ComplexKey<'a> {
42/// name: &'a str,
43/// email: &'a str,
44/// }
45///
46/// impl BiHashItem for Item {
47/// type K1<'a> = u32;
48/// type K2<'a> = ComplexKey<'a>;
49/// fn key1(&self) -> Self::K1<'_> {
50/// self.id
51/// }
52/// fn key2(&self) -> Self::K2<'_> {
53/// ComplexKey { name: &self.name, email: &self.email }
54/// }
55/// bi_upcast!();
56/// }
57///
58/// let mut map = BiHashMap::<Item>::new();
59/// map.insert_unique(Item {
60/// id: 1,
61/// name: "Alice".to_string(),
62/// email: "alice@example.com".to_string(),
63/// value: 42,
64/// })
65/// .unwrap();
66///
67/// // The map is serialized as a list of items.
68/// let serialized = serde_json::to_string(&map).unwrap();
69/// assert_eq!(
70/// serialized,
71/// r#"[{"id":1,"name":"Alice","email":"alice@example.com","value":42}]"#,
72/// );
73/// # }
74/// ```
75impl<T: BiHashItem, S: Clone + BuildHasher, A: Allocator> Serialize
76 for BiHashMap<T, S, A>
77where
78 T: Serialize,
79{
80 fn serialize<Ser: Serializer>(
81 &self,
82 serializer: Ser,
83 ) -> Result<Ser::Ok, Ser::Error> {
84 // Serialize just the items -- don't serialize the indexes. We'll
85 // rebuild the indexes on deserialization.
86 self.items.serialize(serializer)
87 }
88}
89
90/// The `Deserialize` impl for `BiHashMap` deserializes from either a sequence
91/// or a map of items, rebuilding the indexes and producing an error if there
92/// are any duplicates.
93///
94/// In case a map is deserialized, the key is not deserialized or verified
95/// against the value. (In general, verification is not possible because the key
96/// type has a lifetime parameter embedded in it.)
97///
98/// The `fmt::Debug` bound on `T` ensures better error reporting.
99impl<
100 'de,
101 T: BiHashItem + fmt::Debug,
102 S: Clone + BuildHasher + Default,
103 A: Default + Allocator + Clone,
104> Deserialize<'de> for BiHashMap<T, S, A>
105where
106 T: Deserialize<'de>,
107{
108 fn deserialize<D: Deserializer<'de>>(
109 deserializer: D,
110 ) -> Result<Self, D::Error> {
111 deserializer.deserialize_any(SeqVisitor {
112 _marker: PhantomData,
113 hasher: S::default(),
114 alloc: A::default(),
115 })
116 }
117}
118
119impl<
120 'de,
121 T: BiHashItem + fmt::Debug + Deserialize<'de>,
122 S: Clone + BuildHasher,
123 A: Clone + Allocator,
124> BiHashMap<T, S, A>
125{
126 /// Deserializes from a list of items, allocating new storage within the
127 /// provided allocator.
128 pub fn deserialize_in<D: Deserializer<'de>>(
129 deserializer: D,
130 alloc: A,
131 ) -> Result<Self, D::Error>
132 where
133 S: Default,
134 {
135 deserializer.deserialize_any(SeqVisitor {
136 _marker: PhantomData,
137 hasher: S::default(),
138 alloc,
139 })
140 }
141
142 /// Deserializes from a list of items, with the given hasher, using the
143 /// default allocator.
144 pub fn deserialize_with_hasher<D: Deserializer<'de>>(
145 deserializer: D,
146 hasher: S,
147 ) -> Result<Self, D::Error>
148 where
149 A: Default,
150 {
151 deserializer.deserialize_any(SeqVisitor {
152 _marker: PhantomData,
153 hasher,
154 alloc: A::default(),
155 })
156 }
157
158 /// Deserializes from a list of items, with the given hasher, and allocating
159 /// new storage within the provided allocator.
160 pub fn deserialize_with_hasher_in<D: Deserializer<'de>>(
161 deserializer: D,
162 hasher: S,
163 alloc: A,
164 ) -> Result<Self, D::Error> {
165 deserializer.deserialize_any(SeqVisitor {
166 _marker: PhantomData,
167 hasher,
168 alloc,
169 })
170 }
171}
172
173struct SeqVisitor<T, S, A> {
174 _marker: PhantomData<fn() -> T>,
175 hasher: S,
176 alloc: A,
177}
178
179impl<'de, T, S, A> Visitor<'de> for SeqVisitor<T, S, A>
180where
181 T: BiHashItem + Deserialize<'de> + fmt::Debug,
182 S: Clone + BuildHasher,
183 A: Clone + Allocator,
184{
185 type Value = BiHashMap<T, S, A>;
186
187 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
188 formatter
189 .write_str("a sequence or map of items representing a BiHashMap")
190 }
191
192 fn visit_seq<Access>(
193 self,
194 mut seq: Access,
195 ) -> Result<Self::Value, Access::Error>
196 where
197 Access: SeqAccess<'de>,
198 {
199 let mut map = BiHashMap::with_capacity_and_hasher_in(
200 cautious::<T>(seq.size_hint()),
201 self.hasher,
202 self.alloc,
203 );
204
205 while let Some(element) = seq.next_element()? {
206 map.insert_unique(element)
207 .map_err(serde_core::de::Error::custom)?;
208 }
209
210 Ok(map)
211 }
212
213 fn visit_map<Access>(
214 self,
215 mut map_access: Access,
216 ) -> Result<Self::Value, Access::Error>
217 where
218 Access: MapAccess<'de>,
219 {
220 let mut map = BiHashMap::with_capacity_and_hasher_in(
221 cautious::<T>(map_access.size_hint()),
222 self.hasher,
223 self.alloc,
224 );
225
226 while let Some((_, value)) =
227 map_access.next_entry::<serde_core::de::IgnoredAny, T>()?
228 {
229 map.insert_unique(value).map_err(serde_core::de::Error::custom)?;
230 }
231
232 Ok(map)
233 }
234}
235
236/// Marker type for [`BiHashMap`] serialized as a map, for use with serde's
237/// `with` attribute.
238///
239/// The key type [`Self::K1`](BiHashItem::K1) is used as the map key.
240///
241/// # Examples
242///
243/// Use with serde's `with` attribute:
244///
245/// ```
246/// # #[cfg(feature = "default-hasher")] {
247/// use iddqd::{
248/// BiHashItem, BiHashMap, bi_hash_map::BiHashMapAsMap, bi_upcast,
249/// };
250/// use serde::{Deserialize, Serialize};
251///
252/// #[derive(Debug, Serialize, Deserialize)]
253/// struct Item {
254/// id: u32,
255/// name: String,
256/// }
257///
258/// impl BiHashItem for Item {
259/// type K1<'a> = u32;
260/// type K2<'a> = &'a str;
261/// fn key1(&self) -> Self::K1<'_> {
262/// self.id
263/// }
264/// fn key2(&self) -> Self::K2<'_> {
265/// &self.name
266/// }
267/// bi_upcast!();
268/// }
269///
270/// #[derive(Serialize, Deserialize)]
271/// struct Config {
272/// #[serde(with = "BiHashMapAsMap")]
273/// items: BiHashMap<Item>,
274/// }
275/// # }
276/// ```
277///
278/// # Requirements
279///
280/// - For serialization, the key type `K1` must implement [`Serialize`].
281/// - For JSON serialization, `K1` should be string-like or convertible to a string key.
282pub struct BiHashMapAsMap<T, S = DefaultHashBuilder, A: Allocator = Global> {
283 #[expect(clippy::type_complexity)]
284 _marker: PhantomData<fn() -> (T, S, A)>,
285}
286
287struct MapVisitorAsMap<T, S, A> {
288 _marker: PhantomData<fn() -> T>,
289 hasher: S,
290 alloc: A,
291}
292
293impl<'de, T, S, A> Visitor<'de> for MapVisitorAsMap<T, S, A>
294where
295 T: BiHashItem + Deserialize<'de> + fmt::Debug,
296 S: Clone + BuildHasher,
297 A: Clone + Allocator,
298{
299 type Value = BiHashMap<T, S, A>;
300
301 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
302 formatter.write_str("a map with items representing a BiHashMap")
303 }
304
305 fn visit_map<Access>(
306 self,
307 mut map_access: Access,
308 ) -> Result<Self::Value, Access::Error>
309 where
310 Access: MapAccess<'de>,
311 {
312 let mut map = BiHashMap::with_capacity_and_hasher_in(
313 cautious::<T>(map_access.size_hint()),
314 self.hasher,
315 self.alloc,
316 );
317
318 while let Some((_, value)) =
319 map_access.next_entry::<serde_core::de::IgnoredAny, T>()?
320 {
321 map.insert_unique(value).map_err(serde_core::de::Error::custom)?;
322 }
323
324 Ok(map)
325 }
326}
327
328impl<T, S, A> BiHashMapAsMap<T, S, A>
329where
330 S: Clone + BuildHasher,
331 A: Allocator,
332{
333 /// Serializes a `BiHashMap` as a JSON object/map using `key1()` as keys.
334 pub fn serialize<'a, Ser>(
335 map: &BiHashMap<T, S, A>,
336 serializer: Ser,
337 ) -> Result<Ser::Ok, Ser::Error>
338 where
339 T: BiHashItem + Serialize,
340 T: 'a,
341 T::K1<'a>: Serialize,
342 Ser: Serializer,
343 {
344 let mut ser_map = serializer.serialize_map(Some(map.len()))?;
345 for item in map.iter() {
346 let key1 = item.key1();
347 // SAFETY:
348 //
349 // * Lifetime extension: for a type T and two lifetime params 'a and
350 // 'b, T<'a> and T<'b> aren't guaranteed to have the same layout,
351 // but (a) that is true today and (b) it would be shocking and
352 // break half the Rust ecosystem if that were to change in the
353 // future.
354 // * We only use key within the scope of this block before
355 // immediately dropping it. In particular, ser_map.serialize_entry
356 // serializes the key without holding a reference to it.
357 let key1 =
358 unsafe { core::mem::transmute::<T::K1<'_>, T::K1<'a>>(key1) };
359 ser_map.serialize_entry(&key1, item)?;
360 }
361 ser_map.end()
362 }
363
364 /// Deserializes a `BiHashMap` from a JSON object/map.
365 pub fn deserialize<'de, D>(
366 deserializer: D,
367 ) -> Result<BiHashMap<T, S, A>, D::Error>
368 where
369 T: BiHashItem + Deserialize<'de> + fmt::Debug,
370 S: Default,
371 A: Clone + Default,
372 D: Deserializer<'de>,
373 {
374 deserializer.deserialize_map(MapVisitorAsMap {
375 _marker: PhantomData,
376 hasher: S::default(),
377 alloc: A::default(),
378 })
379 }
380}