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    struct DeriveTest_ZeroMap2d<'data> {
358        #[serde(borrow)]
359        _data: ZeroMap2d<'data, u16, str, [u8]>,
360    }
361
362    #[derive(serde::Serialize, serde::Deserialize)]
363    struct DeriveTest_ZeroMap2dBorrowed<'data> {
364        #[serde(borrow)]
365        _data: ZeroMap2dBorrowed<'data, u16, str, [u8]>,
366    }
367
368    const JSON_STR: &str = "{\"1\":{\"1\":\"uno\"},\"2\":{\"2\":\"dos\",\"3\":\"tres\"}}";
369    const BINCODE_BYTES: &[u8] = &[
370        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,
371        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,
372        117, 110, 111, 100, 111, 115, 116, 114, 101, 115,
373    ];
374
375    fn make_map() -> ZeroMap2d<'static, u32, u16, str> {
376        let mut map = ZeroMap2d::new();
377        map.insert(&1, &1, "uno");
378        map.insert(&2, &2, "dos");
379        map.insert(&2, &3, "tres");
380        map
381    }
382
383    #[test]
384    fn test_serde_json() {
385        let map = make_map();
386        let json_str = serde_json::to_string(&map).expect("serialize");
387        assert_eq!(JSON_STR, json_str);
388        let new_map: ZeroMap2d<u32, u16, str> =
389            serde_json::from_str(&json_str).expect("deserialize");
390        assert_eq!(format!("{new_map:?}"), format!("{map:?}"));
391    }
392
393    #[test]
394    fn test_bincode() {
395        let map = make_map();
396        let bincode_bytes = bincode::serialize(&map).expect("serialize");
397        assert_eq!(BINCODE_BYTES, bincode_bytes);
398        let new_map: ZeroMap2d<u32, u16, str> =
399            bincode::deserialize(&bincode_bytes).expect("deserialize");
400        assert_eq!(
401            format!("{new_map:?}"),
402            format!("{map:?}").replace("Owned", "Borrowed"),
403        );
404
405        let new_map: ZeroMap2dBorrowed<u32, u16, str> =
406            bincode::deserialize(&bincode_bytes).expect("deserialize");
407        assert_eq!(
408            format!("{new_map:?}"),
409            format!("{map:?}")
410                .replace("Owned", "Borrowed")
411                .replace("ZeroMap2d", "ZeroMap2dBorrowed")
412        );
413    }
414
415    #[test]
416    fn test_serde_rmp() {
417        let map = make_map();
418        let rmp_buf = rmp_serde::to_vec(&map).expect("serialize");
419        let new_map: ZeroMap2d<u32, u16, str> = rmp_serde::from_slice(&rmp_buf).unwrap();
420        assert_eq!(map, new_map);
421    }
422
423    #[test]
424    fn test_sample_bincode() {
425        // This is the map from the main docs page for ZeroMap2d
426        let mut map: ZeroMap2d<u16, u16, str> = ZeroMap2d::new();
427        map.insert(&1, &2, "three");
428        let bincode_bytes: Vec<u8> = bincode::serialize(&map).expect("serialize");
429        assert_eq!(
430            bincode_bytes.as_slice(),
431            &[
432                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,
433                0, 0, 2, 0, 7, 0, 0, 0, 0, 0, 0, 0, 1, 0, 116, 104, 114, 101, 101
434            ]
435        );
436    }
437}