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