iddqd/id_hash_map/
serde_impls.rs

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