zerovec/map2d/
serde.rs

1// This file is part of ICU4X. For terms of use, please see the file
2// called LICENSE at the top level of the ICU4X source tree
3// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).
4
5use super::{ZeroMap2d, ZeroMap2dBorrowed, ZeroMap2dCursor};
6use crate::map::{MutableZeroVecLike, ZeroMapKV, ZeroVecLike};
7use crate::ZeroVec;
8use alloc::vec::Vec;
9use core::fmt;
10use core::marker::PhantomData;
11use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
12#[cfg(feature = "serde")]
13use serde::ser::{Serialize, SerializeMap, Serializer};
14
15/// This impl requires enabling the optional `serde` Cargo feature of the `zerovec` crate
16#[cfg(feature = "serde")]
17impl<'a, K0, K1, V> Serialize for ZeroMap2d<'a, K0, K1, V>
18where
19    K0: ZeroMapKV<'a> + Serialize + ?Sized + Ord,
20    K1: ZeroMapKV<'a> + Serialize + ?Sized + Ord,
21    V: ZeroMapKV<'a> + Serialize + ?Sized,
22    K0::Container: Serialize,
23    K1::Container: Serialize,
24    V::Container: Serialize,
25{
26    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27    where
28        S: Serializer,
29    {
30        if serializer.is_human_readable() {
31            let mut serde_map = serializer.serialize_map(None)?;
32            for cursor in self.iter0() {
33                K0::Container::zvl_get_as_t(cursor.key0(), |k| serde_map.serialize_key(k))?;
34                let inner_map = ZeroMap2dInnerMapSerialize { cursor };
35                serde_map.serialize_value(&inner_map)?;
36            }
37            serde_map.end()
38        } else {
39            (&self.keys0, &self.joiner, &self.keys1, &self.values).serialize(serializer)
40        }
41    }
42}
43
44/// Helper struct for human-serializing the inner map of a ZeroMap2d
45#[cfg(feature = "serde")]
46struct ZeroMap2dInnerMapSerialize<'a, 'l, K0, K1, V>
47where
48    K0: ZeroMapKV<'a> + ?Sized + Ord,
49    K1: ZeroMapKV<'a> + ?Sized + Ord,
50    V: ZeroMapKV<'a> + ?Sized,
51{
52    pub cursor: ZeroMap2dCursor<'l, 'a, K0, K1, V>,
53}
54
55#[cfg(feature = "serde")]
56impl<'a, 'l, K0, K1, V> Serialize for ZeroMap2dInnerMapSerialize<'a, 'l, K0, K1, V>
57where
58    K0: ZeroMapKV<'a> + Serialize + ?Sized + Ord,
59    K1: ZeroMapKV<'a> + Serialize + ?Sized + Ord,
60    V: ZeroMapKV<'a> + Serialize + ?Sized,
61    K0::Container: Serialize,
62    K1::Container: Serialize,
63    V::Container: Serialize,
64{
65    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
66    where
67        S: Serializer,
68    {
69        let mut serde_map = serializer.serialize_map(None)?;
70        for (key1, v) in self.cursor.iter1() {
71            K1::Container::zvl_get_as_t(key1, |k| serde_map.serialize_key(k))?;
72            V::Container::zvl_get_as_t(v, |v| serde_map.serialize_value(v))?;
73        }
74        serde_map.end()
75    }
76}
77
78/// This impl requires enabling the optional `serde` Cargo feature of the `zerovec` crate
79#[cfg(feature = "serde")]
80impl<'a, K0, K1, V> Serialize for ZeroMap2dBorrowed<'a, K0, K1, V>
81where
82    K0: ZeroMapKV<'a> + Serialize + ?Sized + Ord,
83    K1: ZeroMapKV<'a> + Serialize + ?Sized + Ord,
84    V: ZeroMapKV<'a> + Serialize + ?Sized,
85    K0::Container: Serialize,
86    K1::Container: Serialize,
87    V::Container: Serialize,
88{
89    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
90    where
91        S: Serializer,
92    {
93        ZeroMap2d::<K0, K1, V>::from(*self).serialize(serializer)
94    }
95}
96
97/// Modified example from https://serde.rs/deserialize-map.html
98struct ZeroMap2dMapVisitor<'a, K0, K1, V>
99where
100    K0: ZeroMapKV<'a> + ?Sized + Ord,
101    K1: ZeroMapKV<'a> + ?Sized + Ord,
102    V: ZeroMapKV<'a> + ?Sized,
103{
104    #[expect(clippy::type_complexity)] // it's a marker type, complexity doesn't matter
105    marker: PhantomData<fn() -> (&'a K0::OwnedType, &'a K1::OwnedType, &'a V::OwnedType)>,
106}
107
108impl<'a, K0, K1, V> ZeroMap2dMapVisitor<'a, K0, K1, V>
109where
110    K0: ZeroMapKV<'a> + ?Sized + Ord,
111    K1: ZeroMapKV<'a> + ?Sized + Ord,
112    V: ZeroMapKV<'a> + ?Sized,
113{
114    fn new() -> Self {
115        ZeroMap2dMapVisitor {
116            marker: PhantomData,
117        }
118    }
119}
120
121impl<'a, 'de, K0, K1, V> Visitor<'de> for ZeroMap2dMapVisitor<'a, K0, K1, V>
122where
123    K0: ZeroMapKV<'a> + Ord + ?Sized + Ord,
124    K1: ZeroMapKV<'a> + Ord + ?Sized + Ord,
125    V: ZeroMapKV<'a> + ?Sized,
126    K1::Container: Deserialize<'de>,
127    V::Container: Deserialize<'de>,
128    K0::OwnedType: Deserialize<'de>,
129    K1::OwnedType: Deserialize<'de>,
130    V::OwnedType: Deserialize<'de>,
131{
132    type Value = ZeroMap2d<'a, K0, K1, V>;
133
134    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
135        formatter.write_str("a map produced by ZeroMap2d")
136    }
137
138    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
139    where
140        M: MapAccess<'de>,
141    {
142        let mut map = ZeroMap2d::with_capacity(access.size_hint().unwrap_or(0));
143
144        // On the first level, pull out the K0s and a TupleVecMap of the
145        // K1s and Vs, and then collect them into a ZeroMap2d
146        while let Some((key0, inner_map)) =
147            access.next_entry::<K0::OwnedType, TupleVecMap<K1::OwnedType, V::OwnedType>>()?
148        {
149            for (key1, value) in inner_map.entries.iter() {
150                if map
151                    .try_append(
152                        K0::Container::owned_as_t(&key0),
153                        K1::Container::owned_as_t(key1),
154                        V::Container::owned_as_t(value),
155                    )
156                    .is_some()
157                {
158                    return Err(de::Error::custom(
159                        "ZeroMap2d's keys must be sorted while deserializing",
160                    ));
161                }
162            }
163        }
164
165        Ok(map)
166    }
167}
168
169/// Helper struct for human-deserializing the inner map of a ZeroMap2d
170struct TupleVecMap<K1, V> {
171    pub entries: Vec<(K1, V)>,
172}
173
174struct TupleVecMapVisitor<K1, V> {
175    marker: PhantomData<fn() -> (K1, V)>,
176}
177
178impl<K1, V> TupleVecMapVisitor<K1, V> {
179    fn new() -> Self {
180        TupleVecMapVisitor {
181            marker: PhantomData,
182        }
183    }
184}
185
186impl<'de, K1, V> Visitor<'de> for TupleVecMapVisitor<K1, V>
187where
188    K1: Deserialize<'de>,
189    V: Deserialize<'de>,
190{
191    type Value = TupleVecMap<K1, V>;
192
193    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
194        formatter.write_str("an inner map produced by ZeroMap2d")
195    }
196
197    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
198    where
199        M: MapAccess<'de>,
200    {
201        let mut result = Vec::with_capacity(access.size_hint().unwrap_or(0));
202        while let Some((key1, value)) = access.next_entry::<K1, V>()? {
203            result.push((key1, value));
204        }
205        Ok(TupleVecMap { entries: result })
206    }
207}
208
209impl<'de, K1, V> Deserialize<'de> for TupleVecMap<K1, V>
210where
211    K1: Deserialize<'de>,
212    V: Deserialize<'de>,
213{
214    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215    where
216        D: Deserializer<'de>,
217    {
218        deserializer.deserialize_map(TupleVecMapVisitor::<K1, V>::new())
219    }
220}
221
222/// This impl requires enabling the optional `serde` Cargo feature of the `zerovec` crate
223impl<'de, 'a, K0, K1, V> Deserialize<'de> for ZeroMap2d<'a, K0, K1, V>
224where
225    K0: ZeroMapKV<'a> + Ord + ?Sized,
226    K1: ZeroMapKV<'a> + Ord + ?Sized,
227    V: ZeroMapKV<'a> + ?Sized,
228    K0::Container: Deserialize<'de>,
229    K1::Container: Deserialize<'de>,
230    V::Container: Deserialize<'de>,
231    K0::OwnedType: Deserialize<'de>,
232    K1::OwnedType: Deserialize<'de>,
233    V::OwnedType: Deserialize<'de>,
234    'de: 'a,
235{
236    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
237    where
238        D: Deserializer<'de>,
239    {
240        if deserializer.is_human_readable() {
241            deserializer.deserialize_map(ZeroMap2dMapVisitor::<'a, K0, K1, V>::new())
242        } else {
243            let (keys0, joiner, keys1, values): (
244                K0::Container,
245                ZeroVec<u32>,
246                K1::Container,
247                V::Container,
248            ) = Deserialize::deserialize(deserializer)?;
249            // Invariant 1: len(keys0) == len(joiner)
250            if keys0.zvl_len() != joiner.len() {
251                return Err(de::Error::custom(
252                    "Mismatched keys0 and joiner sizes in ZeroMap2d",
253                ));
254            }
255            // Invariant 2: len(keys1) == len(values)
256            if keys1.zvl_len() != values.zvl_len() {
257                return Err(de::Error::custom(
258                    "Mismatched keys1 and value sizes in ZeroMap2d",
259                ));
260            }
261            // Invariant 3: joiner is sorted
262            if !joiner.zvl_is_ascending() {
263                return Err(de::Error::custom(
264                    "ZeroMap2d deserializing joiner array out of order",
265                ));
266            }
267            // Invariant 4: the last element of joiner is the length of keys1
268            if let Some(last_joiner0) = joiner.last() {
269                if keys1.zvl_len() != last_joiner0 as usize {
270                    return Err(de::Error::custom(
271                        "ZeroMap2d deserializing joiner array malformed",
272                    ));
273                }
274            }
275            let result = Self {
276                keys0,
277                joiner,
278                keys1,
279                values,
280            };
281            // In debug mode, check the optional invariants, too
282            #[cfg(debug_assertions)]
283            result.check_invariants();
284            Ok(result)
285        }
286    }
287}
288
289/// This impl requires enabling the optional `serde` Cargo feature of the `zerovec` crate
290impl<'de, 'a, K0, K1, V> Deserialize<'de> for ZeroMap2dBorrowed<'a, K0, K1, V>
291where
292    K0: ZeroMapKV<'a> + Ord + ?Sized,
293    K1: ZeroMapKV<'a> + Ord + ?Sized,
294    V: ZeroMapKV<'a> + ?Sized,
295    K0::Container: Deserialize<'de>,
296    K1::Container: Deserialize<'de>,
297    V::Container: Deserialize<'de>,
298    K0::OwnedType: Deserialize<'de>,
299    K1::OwnedType: Deserialize<'de>,
300    V::OwnedType: Deserialize<'de>,
301    'de: 'a,
302{
303    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
304    where
305        D: Deserializer<'de>,
306    {
307        if deserializer.is_human_readable() {
308            Err(de::Error::custom(
309                "ZeroMap2dBorrowed cannot be deserialized from human-readable formats",
310            ))
311        } else {
312            let deserialized: ZeroMap2d<'a, K0, K1, V> = ZeroMap2d::deserialize(deserializer)?;
313            let keys0 = if let Some(keys0) = deserialized.keys0.zvl_as_borrowed_inner() {
314                keys0
315            } else {
316                return Err(de::Error::custom(
317                    "ZeroMap2dBorrowed can only deserialize in zero-copy ways",
318                ));
319            };
320            let joiner = if let Some(joiner) = deserialized.joiner.zvl_as_borrowed_inner() {
321                joiner
322            } else {
323                return Err(de::Error::custom(
324                    "ZeroMap2dBorrowed can only deserialize in zero-copy ways",
325                ));
326            };
327            let keys1 = if let Some(keys1) = deserialized.keys1.zvl_as_borrowed_inner() {
328                keys1
329            } else {
330                return Err(de::Error::custom(
331                    "ZeroMap2dBorrowed can only deserialize in zero-copy ways",
332                ));
333            };
334            let values = if let Some(values) = deserialized.values.zvl_as_borrowed_inner() {
335                values
336            } else {
337                return Err(de::Error::custom(
338                    "ZeroMap2dBorrowed can only deserialize in zero-copy ways",
339                ));
340            };
341            Ok(Self {
342                keys0,
343                joiner,
344                keys1,
345                values,
346            })
347        }
348    }
349}
350
351#[cfg(test)]
352#[allow(non_camel_case_types)]
353mod test {
354    use crate::map2d::{ZeroMap2d, ZeroMap2dBorrowed};
355
356    #[derive(serde::Serialize, serde::Deserialize)]
357    #[allow(
358        dead_code,
359        reason = "We are testing that these types can be deserialized, and Tests compatibility of custom impl with Serde derive."
360    )]
361    struct DeriveTest_ZeroMap2d<'data> {
362        #[serde(borrow)]
363        _data: ZeroMap2d<'data, u16, str, [u8]>,
364    }
365
366    #[derive(serde::Serialize, serde::Deserialize)]
367    #[allow(
368        dead_code,
369        reason = "We are testing that these types can be deserialized, and Tests compatibility of custom impl with Serde derive."
370    )]
371    struct DeriveTest_ZeroMap2dBorrowed<'data> {
372        #[serde(borrow)]
373        _data: ZeroMap2dBorrowed<'data, u16, str, [u8]>,
374    }
375
376    const JSON_STR: &str = "{\"1\":{\"1\":\"uno\"},\"2\":{\"2\":\"dos\",\"3\":\"tres\"}}";
377    const BINCODE_BYTES: &[u8] = &[
378        8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0,
379        0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 0, 3, 0, 16, 0, 0, 0, 0, 0, 0, 0, 3, 0, 3, 0, 6, 0,
380        117, 110, 111, 100, 111, 115, 116, 114, 101, 115,
381    ];
382
383    fn make_map() -> ZeroMap2d<'static, u32, u16, str> {
384        let mut map = ZeroMap2d::new();
385        map.insert(&1, &1, "uno");
386        map.insert(&2, &2, "dos");
387        map.insert(&2, &3, "tres");
388        map
389    }
390
391    #[test]
392    fn test_serde_json() {
393        let map = make_map();
394        let json_str = serde_json::to_string(&map).expect("serialize");
395        assert_eq!(JSON_STR, json_str);
396        let new_map: ZeroMap2d<u32, u16, str> =
397            serde_json::from_str(&json_str).expect("deserialize");
398        assert_eq!(format!("{new_map:?}"), format!("{map:?}"));
399    }
400
401    #[test]
402    fn test_bincode() {
403        let map = make_map();
404        let bincode_bytes = bincode::serialize(&map).expect("serialize");
405        assert_eq!(BINCODE_BYTES, bincode_bytes);
406        let new_map: ZeroMap2d<u32, u16, str> =
407            bincode::deserialize(&bincode_bytes).expect("deserialize");
408        assert_eq!(
409            format!("{new_map:?}"),
410            format!("{map:?}").replace("Owned", "Borrowed"),
411        );
412
413        let new_map: ZeroMap2dBorrowed<u32, u16, str> =
414            bincode::deserialize(&bincode_bytes).expect("deserialize");
415        assert_eq!(
416            format!("{new_map:?}"),
417            format!("{map:?}")
418                .replace("Owned", "Borrowed")
419                .replace("ZeroMap2d", "ZeroMap2dBorrowed")
420        );
421    }
422
423    #[test]
424    fn test_serde_rmp() {
425        let map = make_map();
426        let rmp_buf = rmp_serde::to_vec(&map).expect("serialize");
427        let new_map: ZeroMap2d<u32, u16, str> = rmp_serde::from_slice(&rmp_buf).unwrap();
428        assert_eq!(map, new_map);
429    }
430
431    #[test]
432    fn test_sample_bincode() {
433        // This is the map from the main docs page for ZeroMap2d
434        let mut map: ZeroMap2d<u16, u16, str> = ZeroMap2d::new();
435        map.insert(&1, &2, "three");
436        let bincode_bytes: Vec<u8> = bincode::serialize(&map).expect("serialize");
437        assert_eq!(
438            bincode_bytes.as_slice(),
439            &[
440                2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 4, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0,
441                0, 0, 2, 0, 7, 0, 0, 0, 0, 0, 0, 0, 1, 0, 116, 104, 114, 101, 101
442            ]
443        );
444    }
445}