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}