wasm_bindgen_utils/
ser.rs

1use std::collections::{BTreeMap, HashMap};
2use serde::{ser::SerializeStruct, Serialize, Serializer};
3
4/// Serializer fn for serializing Vec\<u8\> as bytes (Uint8Array for js)
5/// Example:
6/// ```ignore
7/// #[derive(serde::Serialize, serde::Deserialize)]
8/// struct A {
9///     #[serde(serialize_with = "serialize_as_bytes")]
10///     field: Vec<u8>,
11/// }
12/// ```
13pub fn serialize_as_bytes<S: Serializer>(val: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
14    serializer.serialize_bytes(val)
15}
16
17/// Serializer fn for serializing u64 as js bigint
18/// Example:
19/// ```ignore
20/// #[derive(serde::Serialize, serde::Deserialize)]
21/// struct A {
22///     #[serde(serialize_with = "serialize_u64_as_bigint")]
23///     field: u64,
24/// }
25/// ```
26pub fn serialize_u64_as_bigint<S: Serializer>(val: &u64, serializer: S) -> Result<S::Ok, S::Error> {
27    serializer.serialize_u128(*val as u128)
28}
29
30/// Serializer fn for serializing i64 as js bigint
31/// Example:
32/// ```ignore
33/// #[derive(serde::Serialize, serde::Deserialize)]
34/// struct A {
35///     #[serde(serialize_with = "serialize_i64_as_bigint")]
36///     field: i64,
37/// }
38/// ```
39pub fn serialize_i64_as_bigint<S: Serializer>(val: &i64, serializer: S) -> Result<S::Ok, S::Error> {
40    serializer.serialize_i128(*val as i128)
41}
42
43/// Serializer fn that serializes HashMap as k/v object.
44/// in js it would be plain js object and not js Map.
45///
46/// The [HashMap]'s entry values should themselves impl
47/// [Serialize] as well.
48///
49/// This provides great level of flexibilty to specify a
50/// specific property of the given type and not all of the
51/// properties to be serialized as js plain object instead
52/// of js Map when wasm_bindgen convert traits are implemented
53/// for the given type by using [impl_wasm_traits](crate::impl_wasm_traits)
54///
55/// Example:
56/// ```ignore
57/// #[derive(serde::Serialize, serde::Deserialize, Tsify)]
58/// struct A {
59///     #[cfg_attr(
60///         target_family = "wasm",
61///         serde(serialize_with = "serialize_hashmap_as_object"),
62///         tsify(type = "Record<string, number>")
63///     )]
64///     field: HashMap<String, u8>,
65/// }
66/// #[cfg(target_family = "wasm")]
67/// impl_all_wasm_traits!(A);
68///
69/// #[wasm_bindgen]
70/// pub fn some_fn() -> A {
71///     let mut rust_map = HashMap::new();
72///     rust_map.insert("key".to_string(), 1);
73///     rust_map.insert("otherKey".to_string(), 2);
74///
75///     // in js when some_fn() is called the result will be:
76///     // { field: { key: 1, otherKey: 2 } }
77///     A { field: rust_map }
78/// }
79/// ```
80pub fn serialize_hashmap_as_object<V, S>(
81    val: &HashMap<String, V>,
82    serializer: S,
83) -> Result<S::Ok, S::Error>
84where
85    V: Serialize,
86    S: Serializer,
87{
88    let mut ser = serializer.serialize_struct("HashMap", val.len())?;
89    for (key, value) in val {
90        // static str is not actually needed since we are dealing
91        // with a hashmap which its keys can change at runtime
92        // so we can safely deref the &str for this purpose
93        let key = unsafe { &*(key.as_str() as *const str) };
94        ser.serialize_field(key, value)?;
95    }
96    ser.end()
97}
98
99/// Same as [serialize_hashmap_as_object] but for `Option<HashMap>`
100pub fn serialize_opt_hashmap_as_object<V, S>(
101    val: &Option<HashMap<String, V>>,
102    serializer: S,
103) -> Result<S::Ok, S::Error>
104where
105    V: Serialize,
106    S: Serializer,
107{
108    match val {
109        Some(ref val) => serialize_hashmap_as_object(val, serializer),
110        None => serializer.serialize_none(),
111    }
112}
113
114/// Serializer fn that serializes BTreeMap as k/v object.
115/// in js it would be plain js object and not js Map.
116///
117/// The [BTreeMap]'s entry values should themselves impl
118/// [Serialize] as well.
119///
120/// This provides great level of flexibilty to specify a
121/// specific property of the given type and not all of the
122/// properties to be serialized as js plain object instead
123/// of js Map when wasm_bindgen convert traits are implemented
124/// for the given type by using [impl_wasm_traits](crate::impl_wasm_traits)
125///
126/// Example:
127/// ```ignore
128/// #[derive(serde::Serialize, serde::Deserialize, Tsify)]
129/// struct A {
130///     #[cfg_attr(
131///         target_family = "wasm",
132///         serde(serialize_with = "serialize_hashmap_as_object"),
133///         tsify(type = "Record<string, number>")
134///     )]
135///     field: BTreeMap<String, u8>,
136/// }
137/// #[cfg(target_family = "wasm")]
138/// impl_all_wasm_traits!(A);
139///
140/// #[wasm_bindgen]
141/// pub fn some_fn() -> A {
142///     let mut rust_map = BTreeMAp::new();
143///     rust_map.insert("key".to_string(), 1);
144///     rust_map.insert("otherKey".to_string(), 2);
145///
146///     // in js when some_fn() is called the result will be:
147///     // { field: { key: 1, otherKey: 2 } }
148///     A { field: rust_map }
149/// }
150/// ```
151pub fn serialize_btreemap_as_object<V, S>(
152    val: &BTreeMap<String, V>,
153    serializer: S,
154) -> Result<S::Ok, S::Error>
155where
156    V: Serialize,
157    S: Serializer,
158{
159    let mut ser = serializer.serialize_struct("BTreeMap", val.len())?;
160    for (key, value) in val {
161        // static str is not actually needed since we are dealing
162        // with a btreemap which its keys can change at runtime
163        // so we can safely deref the &str for this purpose
164        let key = unsafe { &*(key.as_str() as *const str) };
165        ser.serialize_field(key, value)?;
166    }
167    ser.end()
168}
169
170/// Same as [serialize_btreemap_as_object] but for `Option<BTreeMap>`
171pub fn serialize_opt_btreemap_as_object<V, S>(
172    val: &Option<BTreeMap<String, V>>,
173    serializer: S,
174) -> Result<S::Ok, S::Error>
175where
176    V: Serialize,
177    S: Serializer,
178{
179    match val {
180        Some(ref val) => serialize_btreemap_as_object(val, serializer),
181        None => serializer.serialize_none(),
182    }
183}
184
185#[cfg(target_family = "wasm")]
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use std::collections::HashMap;
190    use wasm_bindgen_test::wasm_bindgen_test;
191    use serde_test::{assert_de_tokens, assert_ser_tokens, Token};
192
193    #[wasm_bindgen_test]
194    fn test_byte_serializer() {
195        #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
196        struct Bytes {
197            #[serde(serialize_with = "serialize_as_bytes")]
198            field: Vec<u8>,
199        }
200
201        let bytes = Bytes {
202            field: vec![1, 2, 3, 4, 5, 6],
203        };
204
205        assert_ser_tokens(
206            &bytes,
207            &[
208                Token::Struct {
209                    name: "Bytes",
210                    len: 1,
211                },
212                Token::Str("field"),
213                Token::Bytes(&[1, 2, 3, 4, 5, 6]),
214                Token::StructEnd,
215            ],
216        );
217
218        assert_de_tokens(
219            &bytes,
220            &[
221                Token::Struct {
222                    name: "Bytes",
223                    len: 1,
224                },
225                Token::Str("field"),
226                Token::Seq { len: Some(6) },
227                Token::U8(1),
228                Token::U8(2),
229                Token::U8(3),
230                Token::U8(4),
231                Token::U8(5),
232                Token::U8(6),
233                Token::SeqEnd,
234                Token::StructEnd,
235            ],
236        );
237    }
238
239    #[wasm_bindgen_test]
240    fn test_u64_serializer() {
241        #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
242        struct Bytes {
243            #[serde(serialize_with = "serialize_u64_as_bigint")]
244            field: u64,
245        }
246
247        let bytes = Bytes { field: 123 };
248
249        assert_de_tokens(
250            &bytes,
251            &[
252                Token::Struct {
253                    name: "Bytes",
254                    len: 1,
255                },
256                Token::Str("field"),
257                Token::U64(123),
258                Token::StructEnd,
259            ],
260        );
261    }
262
263    #[wasm_bindgen_test]
264    fn test_i64_serializer() {
265        #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
266        struct Bytes {
267            #[serde(serialize_with = "serialize_i64_as_bigint")]
268            field: i64,
269        }
270
271        let bytes = Bytes { field: 123 };
272
273        assert_de_tokens(
274            &bytes,
275            &[
276                Token::Struct {
277                    name: "Bytes",
278                    len: 1,
279                },
280                Token::Str("field"),
281                Token::I64(123),
282                Token::StructEnd,
283            ],
284        );
285    }
286
287    #[wasm_bindgen_test]
288    fn test_map_serializer() {
289        #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
290        struct Test {
291            #[serde(serialize_with = "serialize_hashmap_as_object")]
292            field: HashMap<String, String>,
293        }
294
295        let mut hashmap = HashMap::new();
296        hashmap.insert("key1".to_string(), "some value".to_string());
297        hashmap.insert("key2".to_string(), "some other value".to_string());
298        let test = Test { field: hashmap };
299
300        assert_ser_tokens(
301            &test,
302            &[
303                Token::Struct {
304                    name: "Test",
305                    len: 1,
306                },
307                Token::Str("field"),
308                Token::Struct {
309                    name: "HashMap",
310                    len: 2,
311                },
312                Token::Str("key1"),
313                Token::Str("some value"),
314                Token::Str("key2"),
315                Token::Str("some other value"),
316                Token::StructEnd,
317                Token::StructEnd,
318            ],
319        );
320
321        assert_de_tokens(
322            &test,
323            &[
324                Token::Struct {
325                    name: "Test",
326                    len: 1,
327                },
328                Token::Str("field"),
329                Token::Map { len: Some(2) },
330                Token::Str("key1"),
331                Token::Str("some value"),
332                Token::Str("key2"),
333                Token::Str("some other value"),
334                Token::MapEnd,
335                Token::StructEnd,
336            ],
337        );
338    }
339
340    #[wasm_bindgen_test]
341    fn test_bmap_serializer() {
342        #[derive(serde::Serialize, serde::Deserialize, PartialEq, Debug)]
343        struct Test {
344            #[serde(serialize_with = "serialize_btreemap_as_object")]
345            field: BTreeMap<String, u8>,
346        }
347
348        let mut bmap = BTreeMap::new();
349        bmap.insert("key1".to_string(), 8);
350        bmap.insert("key2".to_string(), 9);
351        let test = Test { field: bmap };
352
353        assert_ser_tokens(
354            &test,
355            &[
356                Token::Struct {
357                    name: "Test",
358                    len: 1,
359                },
360                Token::Str("field"),
361                Token::Struct {
362                    name: "BTreeMap",
363                    len: 2,
364                },
365                Token::Str("key1"),
366                Token::U8(8),
367                Token::Str("key2"),
368                Token::U8(9),
369                Token::StructEnd,
370                Token::StructEnd,
371            ],
372        );
373
374        assert_de_tokens(
375            &test,
376            &[
377                Token::Struct {
378                    name: "Test",
379                    len: 1,
380                },
381                Token::Str("field"),
382                Token::Map { len: Some(2) },
383                Token::Str("key1"),
384                Token::U8(8),
385                Token::Str("key2"),
386                Token::U8(9),
387                Token::MapEnd,
388                Token::StructEnd,
389            ],
390        );
391    }
392}