1use std::{
6 io,
7 io::{Read, Write},
8};
9
10#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
15pub enum Indentation<'a> {
16 TwoSpace,
18 FourSpace,
20 Tab,
22 Custom(&'a str),
24}
25
26impl Default for Indentation<'_> {
27 fn default() -> Self {
28 Self::TwoSpace
29 }
30}
31
32pub fn format(json: &str, indentation: Indentation) -> String {
38 let mut reader = json.as_bytes();
39 let mut writer = Vec::with_capacity(json.len());
40
41 format_reader_writer(&mut reader, &mut writer, indentation).unwrap();
42 String::from_utf8(writer).unwrap()
43}
44
45pub fn format_reader_writer<R, W>(
51 reader: R,
52 mut writer: W,
53 indentation: Indentation,
54) -> io::Result<()>
55where
56 R: Read,
57 W: Write,
58{
59 let mut escaped = false;
60 let mut in_string = false;
61 let mut indent_level = 0usize;
62 let mut newline_requested = false; for char in reader.bytes() {
65 let char = char?;
66 if in_string {
67 let mut escape_here = false;
68 match char {
69 b'"' => {
70 if !escaped {
71 in_string = false;
72 }
73 }
74 b'\\' => {
75 if !escaped {
76 escape_here = true;
77 }
78 }
79 _ => {}
80 }
81 writer.write_all(&[char])?;
82 escaped = escape_here;
83 } else {
84 let mut auto_push = true;
85 let mut request_newline = false;
86 let old_level = indent_level;
87
88 match char {
89 b'"' => in_string = true,
90 b' ' | b'\n' | b'\r' | b'\t' => continue,
91 b'[' => {
92 indent_level += 1;
93 request_newline = true;
94 }
95 b'{' => {
96 indent_level += 1;
97 request_newline = true;
98 }
99 b'}' | b']' => {
100 indent_level = indent_level.saturating_sub(1);
101 if !newline_requested {
102 writer.write_all(b"\n")?;
104 indent(&mut writer, indent_level, indentation)?;
105 }
106 }
107 b':' => {
108 auto_push = false;
109 writer.write_all(&[char])?;
110 writer.write_all(&[b' '])?;
111 }
112 b',' => {
113 request_newline = true;
114 }
115 _ => {}
116 }
117 if newline_requested && char != b']' && char != b'}' {
118 writer.write_all(b"\n")?;
122 indent(&mut writer, old_level, indentation)?;
123 }
124
125 if auto_push {
126 writer.write_all(&[char])?;
127 }
128
129 newline_requested = request_newline;
130 }
131 }
132
133 writer.write_all(b"\n")?;
135
136 Ok(())
137}
138
139fn indent<W>(writer: &mut W, level: usize, indent_str: Indentation) -> io::Result<()>
140where
141 W: Write,
142{
143 for _ in 0..level {
144 match indent_str {
145 Indentation::TwoSpace => {
146 writer.write_all(b" ")?;
147 }
148 Indentation::FourSpace => {
149 writer.write_all(b" ")?;
150 }
151 Indentation::Tab => {
152 writer.write_all(b"\t")?;
153 }
154 Indentation::Custom(indent) => {
155 writer.write_all(indent.as_bytes())?;
156 }
157 }
158 }
159
160 Ok(())
161}
162
163#[cfg(test)]
164mod test {
165 use super::*;
166
167 #[test]
168 fn echoes_primitive() {
169 let json = "1.35\n";
170 assert_eq!(json, format(json, Indentation::TwoSpace));
171 }
172
173 #[test]
174 fn ignore_whitespace_in_string() {
175 let json = "\" hallo \"\n";
176 assert_eq!(json, format(json, Indentation::TwoSpace));
177 }
178
179 #[test]
180 fn remove_leading_whitespace() {
181 let json = " 0";
182 let expected = "0\n";
183 assert_eq!(expected, format(json, Indentation::TwoSpace));
184 }
185
186 #[test]
187 fn handle_escaped_strings() {
188 let json = " \" hallo \\\" \" ";
189 let expected = "\" hallo \\\" \"\n";
190 assert_eq!(expected, format(json, Indentation::TwoSpace));
191 }
192
193 #[test]
194 fn simple_object() {
195 let json = "{\"a\":0}";
196 let expected = "{
197 \"a\": 0
198}
199";
200 assert_eq!(expected, format(json, Indentation::TwoSpace));
201 }
202
203 #[test]
204 fn simple_array() {
205 let json = "[1,2,null]";
206 let expected = "[
207 1,
208 2,
209 null
210]
211";
212 assert_eq!(expected, format(json, Indentation::TwoSpace));
213 }
214
215 #[test]
216 fn array_of_object() {
217 let json = "[{\"a\": 0}, {}, {\"a\": null}]";
218 let expected = "[
219 {
220 \"a\": 0
221 },
222 {},
223 {
224 \"a\": null
225 }
226]
227";
228
229 assert_eq!(expected, format(json, Indentation::TwoSpace));
230 }
231
232 #[test]
233 fn already_formatted() {
234 let expected = "[
235 {
236 \"a\": 0
237 },
238 {},
239 {
240 \"a\": null
241 }
242]
243";
244
245 assert_eq!(expected, format(expected, Indentation::TwoSpace));
246 }
247
248 #[test]
249 fn contains_crlf() {
250 let json = "[\r\n{\r\n\"a\":0\r\n},\r\n{},\r\n{\r\n\"a\": null\r\n}\r\n]\r\n";
251 let expected = "[
252 {
253 \"a\": 0
254 },
255 {},
256 {
257 \"a\": null
258 }
259]
260";
261
262 assert_eq!(expected, format(json, Indentation::TwoSpace));
263 }
264}