1use crate::error::{Error, Result};
7use std::collections::BTreeMap;
8use std::fmt::Write as _;
9
10#[derive(Debug, Clone, PartialEq)]
15pub enum Json {
16 Null,
17 Bool(bool),
18 Number(f64),
19 String(String),
20 Array(Vec<Json>),
21 Object(BTreeMap<String, Json>),
22}
23
24impl Json {
25 pub fn object<K: Into<String>, I: IntoIterator<Item = (K, Json)>>(pairs: I) -> Self {
27 Json::Object(pairs.into_iter().map(|(k, v)| (k.into(), v)).collect())
28 }
29
30 pub fn get(&self, path: &str) -> Option<&Json> {
34 let mut current = self;
35 for segment in path.split('.') {
36 current = match current {
37 Json::Object(map) => map.get(segment)?,
38 Json::Array(items) => items.get(segment.parse::<usize>().ok()?)?,
39 _ => return None,
40 };
41 }
42 Some(current)
43 }
44
45 pub fn as_str(&self) -> Option<&str> {
46 match self {
47 Json::String(s) => Some(s),
48 _ => None,
49 }
50 }
51
52 pub fn as_f64(&self) -> Option<f64> {
53 match self {
54 Json::Number(n) => Some(*n),
55 _ => None,
56 }
57 }
58
59 pub fn as_i64(&self) -> Option<i64> {
60 self.as_f64().map(|n| n as i64)
61 }
62
63 pub fn as_bool(&self) -> Option<bool> {
64 match self {
65 Json::Bool(b) => Some(*b),
66 _ => None,
67 }
68 }
69
70 pub fn as_array(&self) -> Option<&[Json]> {
71 match self {
72 Json::Array(items) => Some(items),
73 _ => None,
74 }
75 }
76
77 pub fn as_object(&self) -> Option<&BTreeMap<String, Json>> {
78 match self {
79 Json::Object(map) => Some(map),
80 _ => None,
81 }
82 }
83
84 pub fn is_null(&self) -> bool {
85 matches!(self, Json::Null)
86 }
87
88 pub fn to_string_pretty(&self) -> String {
90 let mut out = String::new();
91 self.write(&mut out, Some(2), 0);
92 out
93 }
94
95 fn write(&self, out: &mut String, indent: Option<usize>, depth: usize) {
96 let (newline, pad, pad_close) = match indent {
97 Some(width) => (
98 "\n".to_string(),
99 " ".repeat(width * (depth + 1)),
100 " ".repeat(width * depth),
101 ),
102 None => (String::new(), String::new(), String::new()),
103 };
104 let colon = if indent.is_some() { ": " } else { ":" };
105
106 match self {
107 Json::Null => out.push_str("null"),
108 Json::Bool(true) => out.push_str("true"),
109 Json::Bool(false) => out.push_str("false"),
110 Json::Number(n) => {
111 if n.is_finite() {
112 if n.fract() == 0.0 && n.abs() < 1e15 {
114 let _ = write!(out, "{}", *n as i64);
115 } else {
116 let _ = write!(out, "{n}");
117 }
118 } else {
119 out.push_str("null");
120 }
121 }
122 Json::String(s) => escape_into(s, out),
123 Json::Array(items) => {
124 if items.is_empty() {
125 out.push_str("[]");
126 return;
127 }
128 out.push('[');
129 for (i, item) in items.iter().enumerate() {
130 if i > 0 {
131 out.push(',');
132 }
133 out.push_str(&newline);
134 out.push_str(&pad);
135 item.write(out, indent, depth + 1);
136 }
137 out.push_str(&newline);
138 out.push_str(&pad_close);
139 out.push(']');
140 }
141 Json::Object(map) => {
142 if map.is_empty() {
143 out.push_str("{}");
144 return;
145 }
146 out.push('{');
147 for (i, (key, value)) in map.iter().enumerate() {
148 if i > 0 {
149 out.push(',');
150 }
151 out.push_str(&newline);
152 out.push_str(&pad);
153 escape_into(key, out);
154 out.push_str(colon);
155 value.write(out, indent, depth + 1);
156 }
157 out.push_str(&newline);
158 out.push_str(&pad_close);
159 out.push('}');
160 }
161 }
162 }
163
164 pub fn parse(input: &str) -> Result<Json> {
166 let mut parser = Parser { bytes: input.as_bytes(), pos: 0 };
167 parser.skip_whitespace();
168 let value = parser.value()?;
169 parser.skip_whitespace();
170 if parser.pos < parser.bytes.len() {
171 return Err(parser.error("unexpected trailing characters"));
172 }
173 Ok(value)
174 }
175}
176
177impl std::fmt::Display for Json {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 let mut out = String::new();
181 self.write(&mut out, None, 0);
182 f.write_str(&out)
183 }
184}
185
186fn escape_into(s: &str, out: &mut String) {
187 out.push('"');
188 for ch in s.chars() {
189 match ch {
190 '"' => out.push_str("\\\""),
191 '\\' => out.push_str("\\\\"),
192 '\n' => out.push_str("\\n"),
193 '\r' => out.push_str("\\r"),
194 '\t' => out.push_str("\\t"),
195 '\u{08}' => out.push_str("\\b"),
196 '\u{0c}' => out.push_str("\\f"),
197 '<' => out.push_str("\\u003c"),
199 c if (c as u32) < 0x20 => {
200 let _ = write!(out, "\\u{:04x}", c as u32);
201 }
202 c => out.push(c),
203 }
204 }
205 out.push('"');
206}
207
208impl From<bool> for Json {
209 fn from(v: bool) -> Self {
210 Json::Bool(v)
211 }
212}
213
214impl From<String> for Json {
215 fn from(v: String) -> Self {
216 Json::String(v)
217 }
218}
219
220impl From<&str> for Json {
221 fn from(v: &str) -> Self {
222 Json::String(v.to_string())
223 }
224}
225
226impl<T: Into<Json>> From<Option<T>> for Json {
227 fn from(v: Option<T>) -> Self {
228 v.map_or(Json::Null, Into::into)
229 }
230}
231
232impl<T: Into<Json>> From<Vec<T>> for Json {
233 fn from(v: Vec<T>) -> Self {
234 Json::Array(v.into_iter().map(Into::into).collect())
235 }
236}
237
238macro_rules! impl_from_number {
239 ($($t:ty),*) => {
240 $(impl From<$t> for Json {
241 fn from(v: $t) -> Self {
242 Json::Number(v as f64)
243 }
244 })*
245 };
246}
247impl_from_number!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64);
248
249struct Parser<'a> {
250 bytes: &'a [u8],
251 pos: usize,
252}
253
254impl<'a> Parser<'a> {
255 fn error(&self, message: &str) -> Error {
256 let consumed = &self.bytes[..self.pos.min(self.bytes.len())];
258 let line = consumed.iter().filter(|b| **b == b'\n').count() + 1;
259 let column = consumed.iter().rposition(|b| *b == b'\n').map_or(self.pos, |i| self.pos - i - 1) + 1;
260 Error::Json { line, column, message: message.to_string() }
261 }
262
263 fn peek(&self) -> Option<u8> {
264 self.bytes.get(self.pos).copied()
265 }
266
267 fn skip_whitespace(&mut self) {
268 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
269 self.pos += 1;
270 }
271 }
272
273 fn expect(&mut self, byte: u8) -> Result<()> {
274 if self.peek() == Some(byte) {
275 self.pos += 1;
276 Ok(())
277 } else {
278 Err(self.error(&format!("expected `{}`", byte as char)))
279 }
280 }
281
282 fn literal(&mut self, word: &str, value: Json) -> Result<Json> {
283 if self.bytes[self.pos..].starts_with(word.as_bytes()) {
284 self.pos += word.len();
285 Ok(value)
286 } else {
287 Err(self.error("invalid literal"))
288 }
289 }
290
291 fn value(&mut self) -> Result<Json> {
292 match self.peek() {
293 Some(b'n') => self.literal("null", Json::Null),
294 Some(b't') => self.literal("true", Json::Bool(true)),
295 Some(b'f') => self.literal("false", Json::Bool(false)),
296 Some(b'"') => self.string().map(Json::String),
297 Some(b'[') => self.array(),
298 Some(b'{') => self.object(),
299 Some(b'-' | b'0'..=b'9') => self.number(),
300 Some(_) => Err(self.error("unexpected character")),
301 None => Err(self.error("unexpected end of input")),
302 }
303 }
304
305 fn array(&mut self) -> Result<Json> {
306 self.expect(b'[')?;
307 let mut items = Vec::new();
308 self.skip_whitespace();
309 if self.peek() == Some(b']') {
310 self.pos += 1;
311 return Ok(Json::Array(items));
312 }
313 loop {
314 self.skip_whitespace();
315 items.push(self.value()?);
316 self.skip_whitespace();
317 match self.peek() {
318 Some(b',') => self.pos += 1,
319 Some(b']') => {
320 self.pos += 1;
321 return Ok(Json::Array(items));
322 }
323 _ => return Err(self.error("expected `,` or `]`")),
324 }
325 }
326 }
327
328 fn object(&mut self) -> Result<Json> {
329 self.expect(b'{')?;
330 let mut map = BTreeMap::new();
331 self.skip_whitespace();
332 if self.peek() == Some(b'}') {
333 self.pos += 1;
334 return Ok(Json::Object(map));
335 }
336 loop {
337 self.skip_whitespace();
338 let key = self.string()?;
339 self.skip_whitespace();
340 self.expect(b':')?;
341 self.skip_whitespace();
342 map.insert(key, self.value()?);
343 self.skip_whitespace();
344 match self.peek() {
345 Some(b',') => self.pos += 1,
346 Some(b'}') => {
347 self.pos += 1;
348 return Ok(Json::Object(map));
349 }
350 _ => return Err(self.error("expected `,` or `}`")),
351 }
352 }
353 }
354
355 fn number(&mut self) -> Result<Json> {
356 let start = self.pos;
357 if self.peek() == Some(b'-') {
358 self.pos += 1;
359 }
360 while matches!(self.peek(), Some(b'0'..=b'9' | b'.' | b'e' | b'E' | b'+' | b'-')) {
361 self.pos += 1;
362 }
363 std::str::from_utf8(&self.bytes[start..self.pos])
364 .ok()
365 .and_then(|s| s.parse::<f64>().ok())
366 .map(Json::Number)
367 .ok_or_else(|| self.error("invalid number"))
368 }
369
370 fn string(&mut self) -> Result<String> {
371 self.expect(b'"')?;
372 let mut out = String::new();
373 loop {
374 match self.peek() {
375 None => return Err(self.error("unterminated string")),
376 Some(b'"') => {
377 self.pos += 1;
378 return Ok(out);
379 }
380 Some(b'\\') => {
381 self.pos += 1;
382 let escape = self.peek().ok_or_else(|| self.error("unterminated escape"))?;
383 self.pos += 1;
384 match escape {
385 b'"' => out.push('"'),
386 b'\\' => out.push('\\'),
387 b'/' => out.push('/'),
388 b'n' => out.push('\n'),
389 b'r' => out.push('\r'),
390 b't' => out.push('\t'),
391 b'b' => out.push('\u{08}'),
392 b'f' => out.push('\u{0c}'),
393 b'u' => out.push(self.unicode_escape()?),
394 _ => return Err(self.error("invalid escape sequence")),
395 }
396 }
397 Some(_) => {
398 let start = self.pos;
400 while let Some(b) = self.peek() {
401 if b == b'"' || b == b'\\' {
402 break;
403 }
404 self.pos += 1;
405 }
406 match std::str::from_utf8(&self.bytes[start..self.pos]) {
407 Ok(chunk) => out.push_str(chunk),
408 Err(_) => return Err(self.error("invalid UTF-8 in string")),
409 }
410 }
411 }
412 }
413 }
414
415 fn unicode_escape(&mut self) -> Result<char> {
416 let high = self.hex4()?;
417 if (0xD800..0xDC00).contains(&high) {
419 if self.peek() == Some(b'\\') && self.bytes.get(self.pos + 1) == Some(&b'u') {
420 self.pos += 2;
421 let low = self.hex4()?;
422 if (0xDC00..0xE000).contains(&low) {
423 let combined = 0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00);
424 return char::from_u32(combined).ok_or_else(|| self.error("invalid code point"));
425 }
426 }
427 return Err(self.error("unpaired surrogate"));
428 }
429 char::from_u32(high).ok_or_else(|| self.error("invalid code point"))
430 }
431
432 fn hex4(&mut self) -> Result<u32> {
433 if self.pos + 4 > self.bytes.len() {
434 return Err(self.error("truncated \\u escape"));
435 }
436 let hex = std::str::from_utf8(&self.bytes[self.pos..self.pos + 4])
437 .ok()
438 .and_then(|s| u32::from_str_radix(s, 16).ok())
439 .ok_or_else(|| self.error("invalid \\u escape"))?;
440 self.pos += 4;
441 Ok(hex)
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448
449 #[test]
450 fn parses_and_reserializes_a_document() {
451 let source = r#"{"name":"Rustlavel","stars":42,"tags":["web","rust"],"nested":{"ok":true,"nothing":null}}"#;
452 let value = Json::parse(source).unwrap();
453
454 assert_eq!(value.get("name").unwrap().as_str(), Some("Rustlavel"));
455 assert_eq!(value.get("stars").unwrap().as_i64(), Some(42));
456 assert_eq!(value.get("tags.1").unwrap().as_str(), Some("rust"));
457 assert_eq!(value.get("nested.ok").unwrap().as_bool(), Some(true));
458 assert!(value.get("nested.nothing").unwrap().is_null());
459 assert!(value.get("nested.missing").is_none());
460
461 assert_eq!(
464 value.to_string(),
465 r#"{"name":"Rustlavel","nested":{"nothing":null,"ok":true},"stars":42,"tags":["web","rust"]}"#
466 );
467 assert_eq!(Json::parse(&value.to_string()).unwrap(), value);
468 }
469
470 #[test]
471 fn handles_escapes_and_surrogate_pairs() {
472 let value = Json::parse(r#""line\nbreak é 🚀""#).unwrap();
473 assert_eq!(value.as_str(), Some("line\nbreak é 🚀"));
474
475 let embedded = Json::from("</script>").to_string();
477 assert_eq!(embedded, "\"\\u003c/script>\"");
478 assert_eq!(Json::parse(&embedded).unwrap().as_str(), Some("</script>"));
479 }
480
481 #[test]
482 fn integral_numbers_do_not_grow_a_decimal_point() {
483 assert_eq!(Json::from(42).to_string(), "42");
484 assert_eq!(Json::from(1.5).to_string(), "1.5");
485 }
486
487 #[test]
488 fn reports_position_of_a_syntax_error() {
489 let err = Json::parse("{\n \"a\": tru\n}").unwrap_err();
490 match err {
491 Error::Json { line, .. } => assert_eq!(line, 2),
492 other => panic!("expected a JSON error, got {other:?}"),
493 }
494 }
495
496 #[test]
497 fn pretty_printing_round_trips() {
498 let value = Json::parse(r#"{"a":[1,2],"b":{}}"#).unwrap();
499 let pretty = value.to_string_pretty();
500 assert!(pretty.contains("\n \"a\": ["));
501 assert_eq!(Json::parse(&pretty).unwrap(), value);
502 }
503}