sorted_json/
lib.rs

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    // pretty printer for json that prints the dict keys in sorted order
47    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'; // \x08
193const TT: u8 = b't'; // \x09
194const NN: u8 = b'n'; // \x0A
195const FF: u8 = b'f'; // \x0C
196const RR: u8 = b'r'; // \x0D
197const QU: u8 = b'"'; // \x22
198const BS: u8 = b'\\'; // \x5C
199const U: u8 = b'u'; // \x00...\x1F except the ones above
200
201// Lookup table of escape sequences. A value of b'x' at index i means that byte
202// i is escaped as "\x" in JSON. A value of 0 means that byte i is not escaped.
203#[cfg_attr(rustfmt, rustfmt_skip)]
204static ESCAPE: [u8; 256] = [
205    //  1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
206    U,  U,  U,  U,  U,  U,  U,  U, BB, TT, NN,  U, FF, RR,  U,  U, // 0
207    U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U,  U, // 1
208    0,  0, QU,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 2
209    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 3
210    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 4
211    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, BS,  0,  0,  0, // 5
212    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 6
213    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 7
214    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 8
215    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // 9
216    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // A
217    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // B
218    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // C
219    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // D
220    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // E
221    0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, // F
222];
223
224/// Represents a character escape code in a type-safe manner.
225pub enum CharEscape {
226    /// An escaped quote `"`
227    Quote,
228    /// An escaped reverse solidus `\`
229    ReverseSolidus,
230    /// An escaped solidus `/`
231    Solidus,
232    /// An escaped backspace character (usually escaped as `\b`)
233    Backspace,
234    /// An escaped form feed character (usually escaped as `\f`)
235    FormFeed,
236    /// An escaped line feed character (usually escaped as `\n`)
237    LineFeed,
238    /// An escaped carriage return character (usually escaped as `\r`)
239    CarriageReturn,
240    /// An escaped tab character (usually escaped as `\t`)
241    Tab,
242    /// An escaped ASCII plane control character (usually escaped as
243    /// `\u00XX` where `XX` are two hex characters)
244    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}