serde_json_wasm/
lib.rs

1//! [`serde-json`] for `wasm` programs
2//!
3//! [`serde-json`]: https://crates.io/crates/serde_json
4//!
5//! This version of [`serde-json`] is aimed at applications that run on resource constrained
6//! devices.
7//!
8//! # Current features
9//!
10//! - The error type is a simple C like enum (less overhead, smaller memory footprint)
11//! - (De)serialization doesn't require memory allocations
12//! - Deserialization of integers doesn't go through `u64`; instead the string is directly parsed
13//!   into the requested integer type. This avoids pulling in KBs of compiler intrinsics when
14//!   targeting a non 64-bit architecture.
15//! - Supports deserialization of:
16//!   - `bool`
17//!   - Integers
18//!   - `str` (This is a zero copy operation.) (\*)
19//!   - `Option`
20//!   - Arrays
21//!   - Tuples
22//!   - Structs
23//!   - C like enums
24//! - Supports serialization (compact format only) of:
25//!   - `bool`
26//!   - Integers
27//!   - `str`
28//!   - `Option`
29//!   - Arrays
30//!   - Tuples
31//!   - Structs
32//!   - C like enums
33//!
34//! (\*) Deserialization of strings ignores escaped sequences. Escaped sequences might be supported
35//! in the future using a different Serializer as this operation is not zero copy.
36//!
37//! # Planned features
38//!
39//! - (De)serialization from / into IO objects once `core::io::{Read,Write}` becomes a thing.
40//!
41//! # Non-features
42//!
43//! This is explicitly out of scope
44//!
45//! - Anything that involves dynamic memory allocation
46//!   - Like the dynamic [`Value`](https://docs.rs/serde_json/1.0.11/serde_json/enum.Value.html)
47//!     type
48//!
49//! # MSRV
50//!
51//! This crate is guaranteed to compile on stable Rust 1.31.0 and up. It *might* compile with older
52//! versions but that may change in any new patch release.
53
54#![deny(missing_docs)]
55#![deny(rust_2018_compatibility)]
56#![deny(rust_2018_idioms)]
57// Note: Even though we declare the crate as `no_std`, by default `std` feature
58// is enabled which enables serde’s `std` feature which makes our dependency
59// non-`no_std`.  This `no_std` declaration only makes sure that our code
60// doesn’t depend on `std` directly (except for tests).
61#![no_std]
62
63extern crate alloc;
64#[cfg(test)]
65extern crate std;
66
67pub mod de;
68pub mod ser;
69
70#[doc(inline)]
71pub use self::de::{from_slice, from_str};
72#[doc(inline)]
73pub use self::ser::{to_string, to_vec};
74
75#[cfg(test)]
76mod test {
77    use alloc::borrow::ToOwned;
78    use alloc::collections::BTreeMap;
79    use alloc::string::{String, ToString};
80    use alloc::vec;
81    use alloc::vec::Vec;
82
83    use super::*;
84    use serde_derive::{Deserialize, Serialize};
85
86    #[derive(Debug, Deserialize, Serialize, PartialEq)]
87    struct Address(String);
88
89    #[derive(Debug, Deserialize, Serialize, PartialEq)]
90    struct CommentId(u32);
91
92    #[derive(Debug, Deserialize, Serialize, PartialEq)]
93    enum Model {
94        Comment,
95        Post { category: String, author: Address },
96    }
97
98    #[derive(Debug, Deserialize, Serialize, PartialEq)]
99    struct Stats {
100        views: u64,
101        score: i64,
102    }
103
104    #[derive(Debug, Deserialize, Serialize, PartialEq)]
105    struct Item {
106        model: Model,
107        title: String,
108        content: Option<String>,
109        list: Vec<u32>,
110        published: bool,
111        comments: Vec<CommentId>,
112        stats: Stats,
113        balances: BTreeMap<String, u16>,
114    }
115
116    #[test]
117    fn can_serde() {
118        let min = Item {
119            model: Model::Comment,
120            title: String::new(),
121            content: None,
122            list: vec![],
123            published: false,
124            comments: vec![],
125            stats: Stats { views: 0, score: 0 },
126            balances: BTreeMap::new(),
127        };
128        let mut balances: BTreeMap<String, u16> = BTreeMap::new();
129        balances.insert("chareen".into(), 347);
130        let max = Item {
131            model: Model::Post {
132                category: "fun".to_string(),
133                author: Address("sunnyboy85".to_string()),
134            },
135            title: "Nice message".to_string(),
136            content: Some("Happy \"blogging\" 👏\n\n\tCheers, I'm out\0\0\0".to_string()),
137            list: vec![0, 1, 2, 3, 42, 154841, u32::MAX],
138            published: true,
139            comments: vec![CommentId(2), CommentId(700)],
140            stats: Stats {
141                views: u64::MAX,
142                score: i64::MIN,
143            },
144            balances,
145        };
146
147        // binary
148        assert_eq!(from_slice::<Item>(&to_vec(&min).unwrap()).unwrap(), min);
149        assert_eq!(from_slice::<Item>(&to_vec(&max).unwrap()).unwrap(), max);
150
151        // string
152        assert_eq!(from_str::<Item>(&to_string(&min).unwrap()).unwrap(), min);
153        assert_eq!(from_str::<Item>(&to_string(&max).unwrap()).unwrap(), max);
154    }
155
156    #[test]
157    fn untagged() {
158        #[derive(Debug, Deserialize, Serialize, PartialEq)]
159        #[serde(untagged)]
160        enum UntaggedEnum {
161            S(String),
162            I(i64),
163        }
164
165        let s = UntaggedEnum::S("Some string".to_owned());
166        let i = UntaggedEnum::I(32);
167
168        assert_eq!(from_slice::<UntaggedEnum>(&to_vec(&s).unwrap()).unwrap(), s);
169        assert_eq!(from_slice::<UntaggedEnum>(&to_vec(&i).unwrap()).unwrap(), i);
170
171        assert_eq!(
172            from_str::<UntaggedEnum>(&to_string(&s).unwrap()).unwrap(),
173            s
174        );
175        assert_eq!(
176            from_str::<UntaggedEnum>(&to_string(&i).unwrap()).unwrap(),
177            i
178        );
179    }
180
181    #[test]
182    fn untagged_structures() {
183        #[derive(Debug, Deserialize, Serialize, PartialEq)]
184        #[serde(untagged)]
185        enum ModelOrItem {
186            Model(Model),
187            Item(Item),
188        }
189
190        let model = ModelOrItem::Model(Model::Post {
191            category: "Rust".to_owned(),
192            author: Address("no-reply@domain.com".to_owned()),
193        });
194
195        let mut balances: BTreeMap<String, u16> = BTreeMap::new();
196        balances.insert("chareen".into(), 347);
197
198        let item = ModelOrItem::Item(Item {
199            model: Model::Comment,
200            title: "Title".to_owned(),
201            content: None,
202            list: vec![13, 14],
203            published: true,
204            comments: vec![],
205            stats: Stats {
206                views: 110,
207                score: 12,
208            },
209            balances,
210        });
211
212        assert_eq!(
213            from_slice::<ModelOrItem>(&to_vec(&model).unwrap()).unwrap(),
214            model
215        );
216        assert_eq!(
217            from_slice::<ModelOrItem>(&to_vec(&item).unwrap()).unwrap(),
218            item
219        );
220
221        assert_eq!(
222            from_str::<ModelOrItem>(&to_string(&model).unwrap()).unwrap(),
223            model
224        );
225        assert_eq!(
226            from_str::<ModelOrItem>(&to_string(&item).unwrap()).unwrap(),
227            item
228        );
229    }
230
231    #[test]
232    fn no_stack_overflow() {
233        const AMOUNT: usize = 2000;
234        let mut json = String::from(r#"{"":"#);
235
236        #[derive(Debug, Deserialize, Serialize)]
237        pub struct Person {
238            name: String,
239            age: u8,
240            phones: Vec<String>,
241        }
242
243        for _ in 0..AMOUNT {
244            json.push('[');
245        }
246        for _ in 0..AMOUNT {
247            json.push(']');
248        }
249
250        json.push_str(r#"]        }[[[[[[[[[[[[[[[[[[[[[   ""","age":35,"phones":["#);
251
252        let err = from_str::<Person>(&json).unwrap_err();
253        assert_eq!(err, crate::de::Error::RecursionLimitExceeded);
254    }
255}