Skip to main content

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}