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}