miniserde/json/
ser.rs

1use crate::ser::{Fragment, Map, Seq, Serialize};
2use alloc::borrow::Cow;
3use alloc::boxed::Box;
4use alloc::string::String;
5use alloc::vec::Vec;
6
7/// Serialize any serializable type into a JSON string.
8///
9/// ```rust
10/// use miniserde::{json, Serialize};
11///
12/// #[derive(Serialize, Debug)]
13/// struct Example {
14///     code: u32,
15///     message: String,
16/// }
17///
18/// fn main() {
19///     let example = Example {
20///         code: 200,
21///         message: "reminiscent of Serde".to_owned(),
22///     };
23///
24///     let j = json::to_string(&example);
25///     println!("{}", j);
26/// }
27/// ```
28pub fn to_string<T>(value: &T) -> String
29where
30    T: ?Sized + Serialize,
31{
32    to_string_impl(&value)
33}
34
35struct Serializer<'a> {
36    stack: Vec<Layer<'a>>,
37}
38
39enum Layer<'a> {
40    Seq(Box<dyn Seq + 'a>),
41    Map(Box<dyn Map + 'a>),
42}
43
44impl<'a> Drop for Serializer<'a> {
45    fn drop(&mut self) {
46        // Drop layers in reverse order.
47        while !self.stack.is_empty() {
48            self.stack.pop();
49        }
50    }
51}
52
53fn to_string_impl(value: &dyn Serialize) -> String {
54    let mut out = String::new();
55    let mut serializer = Serializer { stack: Vec::new() };
56    let mut fragment = value.begin();
57
58    loop {
59        match fragment {
60            Fragment::Null => out.push_str("null"),
61            Fragment::Bool(b) => out.push_str(if b { "true" } else { "false" }),
62            Fragment::Str(s) => escape_str(&s, &mut out),
63            Fragment::U64(n) => out.push_str(itoa::Buffer::new().format(n)),
64            Fragment::I64(n) => out.push_str(itoa::Buffer::new().format(n)),
65            Fragment::F64(n) => {
66                if n.is_finite() {
67                    out.push_str(zmij::Buffer::new().format_finite(n));
68                } else {
69                    out.push_str("null");
70                }
71            }
72            Fragment::Seq(mut seq) => {
73                out.push('[');
74                // invariant: `seq` must outlive `first`
75                match unsafe { extend_lifetime!(seq.next() as Option<&dyn Serialize>) } {
76                    Some(first) => {
77                        serializer.stack.push(Layer::Seq(seq));
78                        fragment = first.begin();
79                        continue;
80                    }
81                    None => out.push(']'),
82                }
83            }
84            Fragment::Map(mut map) => {
85                out.push('{');
86                // invariant: `map` must outlive `first`
87                match unsafe { extend_lifetime!(map.next() as Option<(Cow<str>, &dyn Serialize)>) }
88                {
89                    Some((key, first)) => {
90                        escape_str(&key, &mut out);
91                        out.push(':');
92                        serializer.stack.push(Layer::Map(map));
93                        fragment = first.begin();
94                        continue;
95                    }
96                    None => out.push('}'),
97                }
98            }
99        }
100
101        loop {
102            match serializer.stack.last_mut() {
103                Some(Layer::Seq(seq)) => {
104                    // invariant: `seq` must outlive `next`
105                    match unsafe { extend_lifetime!(seq.next() as Option<&dyn Serialize>) } {
106                        Some(next) => {
107                            out.push(',');
108                            fragment = next.begin();
109                            break;
110                        }
111                        None => out.push(']'),
112                    }
113                }
114                Some(Layer::Map(map)) => {
115                    // invariant: `map` must outlive `next`
116                    match unsafe {
117                        extend_lifetime!(map.next() as Option<(Cow<str>, &dyn Serialize)>)
118                    } {
119                        Some((key, next)) => {
120                            out.push(',');
121                            escape_str(&key, &mut out);
122                            out.push(':');
123                            fragment = next.begin();
124                            break;
125                        }
126                        None => out.push('}'),
127                    }
128                }
129                None => return out,
130            }
131            serializer.stack.pop();
132        }
133    }
134}
135
136// Clippy false positive: https://github.com/rust-lang/rust-clippy/issues/5169
137#[allow(clippy::zero_prefixed_literal)]
138fn escape_str(value: &str, out: &mut String) {
139    out.push('"');
140
141    let bytes = value.as_bytes();
142    let mut start = 0;
143
144    for (i, &byte) in bytes.iter().enumerate() {
145        let escape = ESCAPE[byte as usize];
146        if escape == 0 {
147            continue;
148        }
149
150        if start < i {
151            out.push_str(&value[start..i]);
152        }
153
154        match escape {
155            self::BB => out.push_str("\\b"),
156            self::TT => out.push_str("\\t"),
157            self::NN => out.push_str("\\n"),
158            self::FF => out.push_str("\\f"),
159            self::RR => out.push_str("\\r"),
160            self::QU => out.push_str("\\\""),
161            self::BS => out.push_str("\\\\"),
162            self::U => {
163                static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
164                out.push_str("\\u00");
165                out.push(HEX_DIGITS[(byte >> 4) as usize] as char);
166                out.push(HEX_DIGITS[(byte & 0xF) as usize] as char);
167            }
168            _ => unreachable!(),
169        }
170
171        start = i + 1;
172    }
173
174    if start != bytes.len() {
175        out.push_str(&value[start..]);
176    }
177
178    out.push('"');
179}
180
181const BB: u8 = b'b'; // \x08
182const TT: u8 = b't'; // \x09
183const NN: u8 = b'n'; // \x0A
184const FF: u8 = b'f'; // \x0C
185const RR: u8 = b'r'; // \x0D
186const QU: u8 = b'"'; // \x22
187const BS: u8 = b'\\'; // \x5C
188const U: u8 = b'u'; // \x00...\x1F except the ones above
189
190// Lookup table of escape sequences. A value of b'x' at index i means that byte
191// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.
192#[rustfmt::skip]
193static ESCAPE: [u8; 256] = [
194    //  1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
195    U,  U,  U,  U,  U,  U,  U,  U, BB, TT, NN,  U, FF, RR,  U,  U, // 0
196    U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U, // 1
197    0,  0, QU,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 2
198    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 3
199    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 4
200    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, BS,  0,  0,  0, // 5
201    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 6
202    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 7
203    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 8
204    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 9
205    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // A
206    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // B
207    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // C
208    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // D
209    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // E
210    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // F
211];