1extern crate itertools;
2#[cfg(test)]
3#[macro_use]
4extern crate serde_json;
5#[cfg(not(test))]
6extern crate serde_json;
7
8pub fn to_json(v: &serde_json::Value) -> String {
9 let s = String::new();
10 to_json_(v, s, "")
11}
12
13fn escape_json_string(out: &mut String, s: &str) {
14 out.push_str("\"");
15
16 let bytes = s.as_bytes();
17
18 let mut start = 0;
19
20 for (i, &byte) in bytes.iter().enumerate() {
21 let escape = ESCAPE[byte as usize];
22 if escape == 0 {
23 continue;
24 }
25
26 if start < i {
27 out.push_str(&s[start..i]);
28 }
29
30 let char_escape = CharEscape::from_escape_table(escape, byte);
31 out.push_str(&write_char_escape(char_escape));
32
33 start = i + 1;
34 }
35
36 if start != bytes.len() {
37 out.push_str(&s[start..]);
38 }
39
40 out.push_str("\"");
41}
42
43const TAB: &str = " ";
44
45fn to_json_(v: &serde_json::Value, mut out: String, prefix: &str) -> String {
46 let prefix2 = format!("{}{}", prefix, TAB);
48 match v {
49 serde_json::Value::String(s) => escape_json_string(&mut out, s),
50 serde_json::Value::Null => out.push_str("null"),
51 serde_json::Value::Bool(b) => if *b {
52 out.push_str("true")
53 } else {
54 out.push_str("false")
55 },
56 serde_json::Value::Number(n) => out.push_str(&format!("{}", n)),
57 serde_json::Value::Array(a) => {
58 let len = a.len();
59 if len == 0 {
60 out.push_str("[]");
61 } else {
62 out.push_str("[\n");
63 for (idx, item) in itertools::enumerate(a.iter()) {
64 out.push_str(&prefix2);
65 out = to_json_(item, out, &prefix2);
66 if idx < len - 1 {
67 out.push_str(",\n");
68 }
69 }
70 out.push_str("\n");
71 out.push_str(&prefix);
72 out.push_str("]");
73 }
74 }
75 serde_json::Value::Object(m) => {
76 let len = m.len();
77 if len == 0 {
78 out.push_str("{}");
79 } else {
80 out.push_str("{\n");
81 for (idx, k) in itertools::enumerate(itertools::sorted(m.keys())) {
82 let v = m.get(k).unwrap();
83 out.push_str(&prefix2);
84 escape_json_string(&mut out, k);
85 out.push_str(": ");
86 out = to_json_(v, out, &prefix2);
87 if idx < len - 1 {
88 out.push_str(",\n");
89 }
90 }
91 out.push_str("\n");
92 out.push_str(&prefix);
93 out.push_str("}");
94 }
95 }
96 }
97 out
98}
99
100#[cfg(test)]
101mod tests {
102 use serde_json::{value::Number, Value};
103
104 fn a(v: Value, out: &'static str) {
105 assert_eq!(super::to_json(&v), out);
106 }
107
108 #[test]
109 fn to_json() {
110 a(json! {null}, "null");
111 a(json! {1}, "1");
112 a(Value::Number(Number::from_f64(1.0).unwrap()), "1");
113 a(
114 Value::Number(Number::from_f64(-1.0002300e2).unwrap()),
115 "-100.023",
116 );
117 a(json!{"foo"}, "\"foo\"");
118 a(json!{r#"hello "world""#}, r#""hello \"world\"""#);
119 a(
120 json!{[1, 2]},
121 "[
122 1,
123 2
124]",
125 );
126 a(
127 json!{[1, 2, []]},
128 "[
129 1,
130 2,
131 []
132]",
133 );
134 a(
135 json!{[1, 2, [1, 2, [1, 2]]]},
136 "[
137 1,
138 2,
139 [
140 1,
141 2,
142 [
143 1,
144 2
145 ]
146 ]
147]",
148 );
149 a(
150 json!{{"yo": 1, "lo": 2, "no": {}}},
151 "{
152 \"lo\": 2,
153 \"no\": {},
154 \"yo\": 1
155}",
156 );
157 a(
158 json!{{"yo": 1, "lo": 2, "baz": {"one": 1, "do": 2, "tres": {"x": "x", "y": "y"}}}},
159 "{
160 \"baz\": {
161 \"do\": 2,
162 \"one\": 1,
163 \"tres\": {
164 \"x\": \"x\",
165 \"y\": \"y\"
166 }
167 },
168 \"lo\": 2,
169 \"yo\": 1
170}",
171 );
172 a(
173 json!{{"yo": 1, "lo": 2, "baz": {"one": 1, "do": 2, "tres": ["x", "x", "y", "y"]}}},
174 "{
175 \"baz\": {
176 \"do\": 2,
177 \"one\": 1,
178 \"tres\": [
179 \"x\",
180 \"x\",
181 \"y\",
182 \"y\"
183 ]
184 },
185 \"lo\": 2,
186 \"yo\": 1
187}",
188 );
189 }
190}
191
192const 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'; #[cfg_attr(rustfmt, rustfmt_skip)]
204static ESCAPE: [u8; 256] = [
205 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, ];
223
224pub enum CharEscape {
226 Quote,
228 ReverseSolidus,
230 Solidus,
232 Backspace,
234 FormFeed,
236 LineFeed,
238 CarriageReturn,
240 Tab,
242 AsciiControl(u8),
245}
246
247impl CharEscape {
248 #[inline]
249 fn from_escape_table(escape: u8, byte: u8) -> CharEscape {
250 match escape {
251 self::BB => CharEscape::Backspace,
252 self::TT => CharEscape::Tab,
253 self::NN => CharEscape::LineFeed,
254 self::FF => CharEscape::FormFeed,
255 self::RR => CharEscape::CarriageReturn,
256 self::QU => CharEscape::Quote,
257 self::BS => CharEscape::ReverseSolidus,
258 self::U => CharEscape::AsciiControl(byte),
259 _ => unreachable!(),
260 }
261 }
262}
263
264#[inline]
265fn write_char_escape(char_escape: CharEscape) -> String {
266 use self::CharEscape::*;
267
268 let mut out: Vec<u8> = vec![];
269 match char_escape {
270 Quote => out.extend(b"\\\""),
271 ReverseSolidus => out.extend(b"\\\\"),
272 Solidus => out.extend(b"\\/"),
273 Backspace => out.extend(b"\\b"),
274 FormFeed => out.extend(b"\\f"),
275 LineFeed => out.extend(b"\\n"),
276 CarriageReturn => out.extend(b"\\r"),
277 Tab => out.extend(b"\\t"),
278 AsciiControl(byte) => {
279 static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
280 let bytes = &[
281 b'\\',
282 b'u',
283 b'0',
284 b'0',
285 HEX_DIGITS[(byte >> 4) as usize],
286 HEX_DIGITS[(byte & 0xF) as usize],
287 ];
288 out.extend(bytes);
289 }
290 };
291 String::from_utf8(out).unwrap()
292}