Skip to main content

iddqd/id_hash_map/
serde_impls.rs

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