zerovec/map/
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::{MutableZeroVecLike, ZeroMap, ZeroMapBorrowed, ZeroMapKV, ZeroVecLike};
6use core::fmt;
7use core::marker::PhantomData;
8use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
9#[cfg(feature = "serde")]
10use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer};
11
12/// This impl requires enabling the optional `serde` Cargo feature of the `zerovec` crate
13#[cfg(feature = "serde")]
14impl<'a, K, V> Serialize for ZeroMap<'a, K, V>
15where
16    K: ZeroMapKV<'a> + Serialize + ?Sized + Ord,
17    V: ZeroMapKV<'a> + Serialize + ?Sized,
18    K::Container: Serialize,
19    V::Container: Serialize,
20{
21    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
22    where
23        S: Serializer,
24    {
25        if serializer.is_human_readable() {
26            // Many human-readable formats don't support values other
27            // than numbers and strings as map keys. For them, we can serialize
28            // as a vec of tuples instead
29            if let Some(k) = self.iter_keys().next() {
30                if !K::Container::zvl_get_as_t(k, super::serde_helpers::is_num_or_string) {
31                    let mut seq = serializer.serialize_seq(Some(self.len()))?;
32                    for (k, v) in self.iter() {
33                        K::Container::zvl_get_as_t(k, |k| {
34                            V::Container::zvl_get_as_t(v, |v| seq.serialize_element(&(k, v)))
35                        })?;
36                    }
37                    return seq.end();
38                }
39            }
40            let mut map = serializer.serialize_map(Some(self.len()))?;
41            for (k, v) in self.iter() {
42                K::Container::zvl_get_as_t(k, |k| map.serialize_key(k))?;
43                V::Container::zvl_get_as_t(v, |v| map.serialize_value(v))?;
44            }
45            map.end()
46        } else {
47            (&self.keys, &self.values).serialize(serializer)
48        }
49    }
50}
51
52/// This impl requires enabling the optional `serde` Cargo feature of the `zerovec` crate
53#[cfg(feature = "serde")]
54impl<'a, K, V> Serialize for ZeroMapBorrowed<'a, K, V>
55where
56    K: ZeroMapKV<'a> + Serialize + ?Sized + Ord,
57    V: ZeroMapKV<'a> + Serialize + ?Sized,
58    K::Container: Serialize,
59    V::Container: Serialize,
60{
61    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62    where
63        S: Serializer,
64    {
65        ZeroMap::<K, V>::from(*self).serialize(serializer)
66    }
67}
68
69/// Modified example from https://serde.rs/deserialize-map.html
70struct ZeroMapMapVisitor<'a, K, V>
71where
72    K: ZeroMapKV<'a> + ?Sized + Ord,
73    V: ZeroMapKV<'a> + ?Sized,
74{
75    #[expect(clippy::type_complexity)] // it's a marker type, complexity doesn't matter
76    marker: PhantomData<fn() -> (&'a K::OwnedType, &'a V::OwnedType)>,
77}
78
79impl<'a, K, V> ZeroMapMapVisitor<'a, K, V>
80where
81    K: ZeroMapKV<'a> + ?Sized + Ord,
82    V: ZeroMapKV<'a> + ?Sized,
83{
84    fn new() -> Self {
85        ZeroMapMapVisitor {
86            marker: PhantomData,
87        }
88    }
89}
90
91impl<'a, 'de, K, V> Visitor<'de> for ZeroMapMapVisitor<'a, K, V>
92where
93    K: ZeroMapKV<'a> + Ord + ?Sized,
94    V: ZeroMapKV<'a> + ?Sized,
95    K::OwnedType: Deserialize<'de>,
96    V::OwnedType: Deserialize<'de>,
97{
98    type Value = ZeroMap<'a, K, V>;
99
100    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
101        formatter.write_str("a map produced by ZeroMap")
102    }
103
104    fn visit_seq<S>(self, mut access: S) -> Result<Self::Value, S::Error>
105    where
106        S: SeqAccess<'de>,
107    {
108        let mut map = ZeroMap::with_capacity(access.size_hint().unwrap_or(0));
109
110        // While there are entries remaining in the input, add them
111        // into our map.
112        while let Some((key, value)) = access.next_element::<(K::OwnedType, V::OwnedType)>()? {
113            // Try to append it at the end, hoping for a sorted map.
114            // If not sorted, return an error
115            // a serialized map that came from another ZeroMap
116            if map
117                .try_append(
118                    K::Container::owned_as_t(&key),
119                    V::Container::owned_as_t(&value),
120                )
121                .is_some()
122            {
123                return Err(de::Error::custom(
124                    "ZeroMap's keys must be sorted while deserializing",
125                ));
126            }
127        }
128
129        Ok(map)
130    }
131
132    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
133    where
134        M: MapAccess<'de>,
135    {
136        let mut map = ZeroMap::with_capacity(access.size_hint().unwrap_or(0));
137
138        // While there are entries remaining in the input, add them
139        // into our map.
140        while let Some((key, value)) = access.next_entry::<K::OwnedType, V::OwnedType>()? {
141            // Try to append it at the end, hoping for a sorted map.
142            // If not sorted, return an error
143            // a serialized map that came from another ZeroMap
144            if map
145                .try_append(
146                    K::Container::owned_as_t(&key),
147                    V::Container::owned_as_t(&value),
148                )
149                .is_some()
150            {
151                return Err(de::Error::custom(
152                    "ZeroMap's keys must be sorted while deserializing",
153                ));
154            }
155        }
156
157        Ok(map)
158    }
159}
160
161/// This impl requires enabling the optional `serde` Cargo feature of the `zerovec` crate
162impl<'de, 'a, K, V> Deserialize<'de> for ZeroMap<'a, K, V>
163where
164    K: ZeroMapKV<'a> + Ord + ?Sized,
165    V: ZeroMapKV<'a> + ?Sized,
166    K::Container: Deserialize<'de>,
167    V::Container: Deserialize<'de>,
168    K::OwnedType: Deserialize<'de>,
169    V::OwnedType: Deserialize<'de>,
170    'de: 'a,
171{
172    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
173    where
174        D: Deserializer<'de>,
175    {
176        if deserializer.is_human_readable() {
177            deserializer.deserialize_any(ZeroMapMapVisitor::<'a, K, V>::new())
178        } else {
179            let (keys, values): (K::Container, V::Container) =
180                Deserialize::deserialize(deserializer)?;
181            if keys.zvl_len() != values.zvl_len() {
182                return Err(de::Error::custom(
183                    "Mismatched key and value sizes in ZeroMap",
184                ));
185            }
186            // #1433: If keys are out of order, treat it as GIGO.
187            debug_assert!(keys.zvl_is_ascending());
188            Ok(Self { keys, values })
189        }
190    }
191}
192
193// /// This impl requires enabling the optional `serde` Cargo feature of the `zerovec` crate
194impl<'de, 'a, K, V> Deserialize<'de> for ZeroMapBorrowed<'a, K, V>
195where
196    K: ZeroMapKV<'a> + Ord + ?Sized,
197    V: ZeroMapKV<'a> + ?Sized,
198    K::Container: Deserialize<'de>,
199    V::Container: Deserialize<'de>,
200    K::OwnedType: Deserialize<'de>,
201    V::OwnedType: Deserialize<'de>,
202    'de: 'a,
203{
204    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
205    where
206        D: Deserializer<'de>,
207    {
208        if deserializer.is_human_readable() {
209            Err(de::Error::custom(
210                "ZeroMapBorrowed cannot be deserialized from human-readable formats",
211            ))
212        } else {
213            let deserialized: ZeroMap<'a, K, V> = ZeroMap::deserialize(deserializer)?;
214            let keys = if let Some(keys) = deserialized.keys.zvl_as_borrowed_inner() {
215                keys
216            } else {
217                return Err(de::Error::custom(
218                    "ZeroMapBorrowed can only deserialize in zero-copy ways",
219                ));
220            };
221            let values = if let Some(values) = deserialized.values.zvl_as_borrowed_inner() {
222                values
223            } else {
224                return Err(de::Error::custom(
225                    "ZeroMapBorrowed can only deserialize in zero-copy ways",
226                ));
227            };
228            Ok(Self { keys, values })
229        }
230    }
231}
232
233#[cfg(test)]
234#[allow(non_camel_case_types)]
235mod test {
236    use crate::{map::ZeroMapBorrowed, ZeroMap};
237
238    #[derive(serde::Serialize, serde::Deserialize)]
239    #[expect(
240        dead_code,
241        reason = "Tests compatibility of custom impl with Serde derive."
242    )]
243    struct DeriveTest_ZeroMap<'data> {
244        #[serde(borrow)]
245        _data: ZeroMap<'data, str, [u8]>,
246    }
247
248    #[derive(serde::Serialize, serde::Deserialize)]
249    #[expect(
250        dead_code,
251        reason = "Tests compatibility of custom impl with Serde derive."
252    )]
253    struct DeriveTest_ZeroMapBorrowed<'data> {
254        #[serde(borrow)]
255        _data: ZeroMapBorrowed<'data, str, [u8]>,
256    }
257
258    const JSON_STR: &str = "{\"1\":\"uno\",\"2\":\"dos\",\"3\":\"tres\"}";
259    const BINCODE_BYTES: &[u8] = &[
260        12, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 3, 0,
261        3, 0, 6, 0, 117, 110, 111, 100, 111, 115, 116, 114, 101, 115,
262    ];
263
264    fn make_map() -> ZeroMap<'static, u32, str> {
265        let mut map = ZeroMap::new();
266        map.insert(&1, "uno");
267        map.insert(&2, "dos");
268        map.insert(&3, "tres");
269        map
270    }
271
272    #[test]
273    fn test_serde_json() {
274        let map = make_map();
275        let json_str = serde_json::to_string(&map).expect("serialize");
276        assert_eq!(JSON_STR, json_str);
277        let new_map: ZeroMap<u32, str> = serde_json::from_str(&json_str).expect("deserialize");
278        assert_eq!(
279            new_map.iter().collect::<Vec<_>>(),
280            map.iter().collect::<Vec<_>>()
281        );
282    }
283
284    #[test]
285    fn test_serde_json_complex_key() {
286        let mut map = ZeroMap::new();
287        map.insert(&(1, 1), "uno");
288        map.insert(&(2, 2), "dos");
289        map.insert(&(3, 3), "tres");
290        let json_str = serde_json::to_string(&map).expect("serialize");
291        assert_eq!(
292            json_str,
293            "[[[1,1],\"uno\"],[[2,2],\"dos\"],[[3,3],\"tres\"]]"
294        );
295        let new_map: ZeroMap<(u32, u32), str> =
296            serde_json::from_str(&json_str).expect("deserialize");
297        assert_eq!(
298            new_map.iter().collect::<Vec<_>>(),
299            map.iter().collect::<Vec<_>>()
300        );
301    }
302
303    #[test]
304    fn test_bincode() {
305        let map = make_map();
306        let bincode_bytes = bincode::serialize(&map).expect("serialize");
307        assert_eq!(BINCODE_BYTES, bincode_bytes);
308        let new_map: ZeroMap<u32, str> = bincode::deserialize(&bincode_bytes).expect("deserialize");
309        assert_eq!(
310            new_map.iter().collect::<Vec<_>>(),
311            map.iter().collect::<Vec<_>>()
312        );
313
314        let new_map: ZeroMapBorrowed<u32, str> =
315            bincode::deserialize(&bincode_bytes).expect("deserialize");
316        assert_eq!(
317            new_map.iter().collect::<Vec<_>>(),
318            map.iter().collect::<Vec<_>>()
319        );
320    }
321}