1use itertools;
4
5pub fn to_json(v: &serde_json::Value) -> String {
6 let s = String::new();
7 to_json_(v, s, "")
8}
9
10fn escape_json_string(out: &mut String, s: &str) {
11 out.push('\"');
12
13 let bytes = s.as_bytes();
14
15 let mut start = 0;
16
17 for (i, &byte) in bytes.iter().enumerate() {
18 let escape = ESCAPE[byte as usize];
19 if escape == 0 {
20 continue;
21 }
22
23 if start < i {
24 out.push_str(&s[start..i]);
25 }
26
27 let char_escape = CharEscape::from_escape_table(escape, byte);
28 out.push_str(&write_char_escape(char_escape));
29
30 start = i + 1;
31 }
32
33 if start != bytes.len() {
34 out.push_str(&s[start..]);
35 }
36
37 out.push('\"');
38}
39
40const TAB: &str = " ";
41
42fn to_json_(v: &serde_json::Value, mut out: String, prefix: &str) -> String {
43 let prefix2 = format!("{}{}", prefix, TAB);
45 match v {
46 serde_json::Value::String(s) => escape_json_string(&mut out, s),
47 serde_json::Value::Null => out.push_str("null"),
48 serde_json::Value::Bool(b) => {
49 if *b {
50 out.push_str("true")
51 } else {
52 out.push_str("false")
53 }
54 }
55 serde_json::Value::Number(n) => out.push_str(&format!("{}", n)),
56 serde_json::Value::Array(a) => {
57 let len = a.len();
58 if len == 0 {
59 out.push_str("[]");
60 } else {
61 out.push_str("[\n");
62 for (idx, item) in itertools::enumerate(a.iter()) {
63 out.push_str(&prefix2);
64 out = to_json_(item, out, &prefix2);
65 if idx < len - 1 {
66 out.push_str(",\n");
67 }
68 }
69 out.push('\n');
70 out.push_str(prefix);
71 out.push(']');
72 }
73 }
74 serde_json::Value::Object(m) => {
75 let len = m.len();
76 if len == 0 {
77 out.push_str("{}");
78 } else {
79 out.push_str("{\n");
80 for (idx, k) in itertools::enumerate(itertools::sorted(m.keys())) {
81 let v = m.get(k).unwrap();
82 out.push_str(&prefix2);
83 escape_json_string(&mut out, k);
84 out.push_str(": ");
85 out = to_json_(v, out, &prefix2);
86 if idx < len - 1 {
87 out.push_str(",\n");
88 }
89 }
90 out.push('\n');
91 out.push_str(prefix);
92 out.push('}');
93 }
94 }
95 }
96 out
97}
98
99const BB: u8 = b'b'; const TT: u8 = b't'; const NN: u8 = b'n'; const FF: u8 = b'f'; const RR: u8 = b'r'; const QU: u8 = b'"'; const BS: u8 = b'\\'; const U: u8 = b'u'; #[rustfmt::skip]
111static ESCAPE: [u8; 256] = [
112 U, U, U, U, U, U, U, U, BB, TT, NN, U, FF, RR, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, 0, 0, QU, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, BS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
130
131pub enum CharEscape {
133 Quote,
135 ReverseSolidus,
137 Solidus,
139 Backspace,
141 FormFeed,
143 LineFeed,
145 CarriageReturn,
147 Tab,
149 AsciiControl(u8),
152}
153
154impl CharEscape {
155 #[inline]
156 fn from_escape_table(escape: u8, byte: u8) -> CharEscape {
157 match escape {
158 self::BB => CharEscape::Backspace,
159 self::TT => CharEscape::Tab,
160 self::NN => CharEscape::LineFeed,
161 self::FF => CharEscape::FormFeed,
162 self::RR => CharEscape::CarriageReturn,
163 self::QU => CharEscape::Quote,
164 self::BS => CharEscape::ReverseSolidus,
165 self::U => CharEscape::AsciiControl(byte),
166 _ => unreachable!(),
167 }
168 }
169}
170
171#[inline]
172fn write_char_escape(char_escape: CharEscape) -> String {
173 use self::CharEscape::*;
174
175 let mut out: Vec<u8> = vec![];
176 match char_escape {
177 Quote => out.extend(b"\\\""),
178 ReverseSolidus => out.extend(b"\\\\"),
179 Solidus => out.extend(b"\\/"),
180 Backspace => out.extend(b"\\b"),
181 FormFeed => out.extend(b"\\f"),
182 LineFeed => out.extend(b"\\n"),
183 CarriageReturn => out.extend(b"\\r"),
184 Tab => out.extend(b"\\t"),
185 AsciiControl(byte) => {
186 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
187 let bytes = &[
188 b'\\',
189 b'u',
190 b'0',
191 b'0',
192 HEX_DIGITS[(byte >> 4) as usize],
193 HEX_DIGITS[(byte & 0xF) as usize],
194 ];
195 out.extend(bytes);
196 }
197 };
198 String::from_utf8(out).unwrap()
199}
200
201#[cfg(test)]
202mod tests {
203 use serde_json::{json, value::Number, Value};
204
205 fn a(v: Value, out: &'static str) {
206 assert_eq!(super::to_json(&v), out);
207 }
208
209 #[test]
210 fn to_json() {
211 a(json! {null}, "null");
212 a(json! {1}, "1");
213 a(Value::Number(Number::from_f64(1.0).unwrap()), "1.0");
214 a(
215 Value::Number(Number::from_f64(-1.0002300e2).unwrap()),
216 "-100.023",
217 );
218 a(json! {"foo"}, "\"foo\"");
219 a(json! {r#"hello "world""#}, r#""hello \"world\"""#);
220 a(
221 json! {[1, 2]},
222 "[
223 1,
224 2
225]",
226 );
227 a(
228 json! {[1, 2, []]},
229 "[
230 1,
231 2,
232 []
233]",
234 );
235 a(
236 json! {[1, 2, [1, 2, [1, 2]]]},
237 "[
238 1,
239 2,
240 [
241 1,
242 2,
243 [
244 1,
245 2
246 ]
247 ]
248]",
249 );
250 a(
251 json! {{"yo": 1, "lo": 2, "no": {}}},
252 "{
253 \"lo\": 2,
254 \"no\": {},
255 \"yo\": 1
256}",
257 );
258 a(
259 json! {{"yo": 1, "lo": 2, "baz": {"one": 1, "do": 2, "tres": {"x": "x", "y": "y"}}}},
260 "{
261 \"baz\": {
262 \"do\": 2,
263 \"one\": 1,
264 \"tres\": {
265 \"x\": \"x\",
266 \"y\": \"y\"
267 }
268 },
269 \"lo\": 2,
270 \"yo\": 1
271}",
272 );
273 a(
274 json! {{"yo": 1, "lo": 2, "baz": {"one": 1, "do": 2, "tres": ["x", "x", "y", "y"]}}},
275 "{
276 \"baz\": {
277 \"do\": 2,
278 \"one\": 1,
279 \"tres\": [
280 \"x\",
281 \"x\",
282 \"y\",
283 \"y\"
284 ]
285 },
286 \"lo\": 2,
287 \"yo\": 1
288}",
289 );
290 }
291}