1#![forbid(unsafe_code)]
14#![deny(missing_docs)]
15#![warn(clippy::all)]
16
17use std::collections::BTreeMap;
18use std::fmt::Write as _;
19
20#[derive(Debug, Clone, PartialEq)]
22pub enum Json {
23 Null,
25 Bool(bool),
27 Number(f64),
29 String(String),
31 Array(Vec<Json>),
33 Object(BTreeMap<String, Json>),
35}
36
37impl Json {
38 pub fn get(&self, key: &str) -> Option<&Json> {
40 match self {
41 Json::Object(m) => m.get(key),
42 _ => None,
43 }
44 }
45
46 pub fn as_str(&self) -> Option<&str> {
48 match self {
49 Json::String(s) => Some(s),
50 _ => None,
51 }
52 }
53
54 pub fn as_i64(&self) -> Option<i64> {
56 match self {
57 Json::Number(n) => Some(*n as i64),
58 _ => None,
59 }
60 }
61
62 pub fn as_array(&self) -> Option<&[Json]> {
68 match self {
69 Json::Array(items) => Some(items),
70 _ => None,
71 }
72 }
73
74 pub fn as_f64(&self) -> Option<f64> {
79 match self {
80 Json::Number(n) => Some(*n),
81 _ => None,
82 }
83 }
84
85 pub fn as_bool(&self) -> Option<bool> {
87 match self {
88 Json::Bool(b) => Some(*b),
89 _ => None,
90 }
91 }
92
93 pub fn object<const N: usize>(pairs: [(&str, Json); N]) -> Json {
95 let mut m = BTreeMap::new();
96 for (k, v) in pairs {
97 m.insert(k.to_string(), v);
98 }
99 Json::Object(m)
100 }
101
102 pub fn str(s: impl Into<String>) -> Json {
104 Json::String(s.into())
105 }
106
107 pub fn num(n: impl Into<f64>) -> Json {
109 Json::Number(n.into())
110 }
111
112 fn write(&self, out: &mut String) {
113 match self {
114 Json::Null => out.push_str("null"),
115 Json::Bool(true) => out.push_str("true"),
116 Json::Bool(false) => out.push_str("false"),
117 Json::Number(n) => {
118 if n.fract() == 0.0 && n.is_finite() && n.abs() < 9e15 {
119 let _ = write!(out, "{}", *n as i64);
120 } else {
121 let _ = write!(out, "{n}");
122 }
123 }
124 Json::String(s) => write_string(s, out),
125 Json::Array(items) => {
126 out.push('[');
127 for (i, item) in items.iter().enumerate() {
128 if i > 0 {
129 out.push(',');
130 }
131 item.write(out);
132 }
133 out.push(']');
134 }
135 Json::Object(map) => {
136 out.push('{');
137 for (i, (k, v)) in map.iter().enumerate() {
138 if i > 0 {
139 out.push(',');
140 }
141 write_string(k, out);
142 out.push(':');
143 v.write(out);
144 }
145 out.push('}');
146 }
147 }
148 }
149}
150
151impl std::fmt::Display for Json {
152 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 let mut out = String::new();
155 self.write(&mut out);
156 f.write_str(&out)
157 }
158}
159
160fn write_string(s: &str, out: &mut String) {
161 out.push('"');
162 for c in s.chars() {
163 match c {
164 '"' => out.push_str("\\\""),
165 '\\' => out.push_str("\\\\"),
166 '\n' => out.push_str("\\n"),
167 '\r' => out.push_str("\\r"),
168 '\t' => out.push_str("\\t"),
169 c if (c as u32) < 0x20 => {
170 let _ = write!(out, "\\u{:04x}", c as u32);
171 }
172 c => out.push(c),
173 }
174 }
175 out.push('"');
176}
177
178pub const MAX_DEPTH: usize = 64;
191
192pub fn parse(input: &str) -> Result<Json, String> {
196 let bytes: Vec<char> = input.chars().collect();
197 let mut p = Parser {
198 input: &bytes,
199 pos: 0,
200 depth: 0,
201 };
202 p.skip_ws();
203 let value = p.value()?;
204 p.skip_ws();
205 if p.pos != p.input.len() {
206 return Err(format!("trailing input at position {}", p.pos));
207 }
208 Ok(value)
209}
210
211struct Parser<'a> {
212 input: &'a [char],
213 pos: usize,
214 depth: usize,
216}
217
218impl Parser<'_> {
219 fn peek(&self) -> Option<char> {
220 self.input.get(self.pos).copied()
221 }
222
223 fn bump(&mut self) -> Option<char> {
224 let c = self.peek();
225 if c.is_some() {
226 self.pos += 1;
227 }
228 c
229 }
230
231 fn skip_ws(&mut self) {
232 while matches!(self.peek(), Some(' ' | '\t' | '\n' | '\r')) {
233 self.pos += 1;
234 }
235 }
236
237 fn expect(&mut self, c: char) -> Result<(), String> {
238 if self.bump() == Some(c) {
239 Ok(())
240 } else {
241 Err(format!("expected '{c}' at position {}", self.pos))
242 }
243 }
244
245 fn literal(&mut self, word: &str) -> Result<(), String> {
246 for c in word.chars() {
247 if self.bump() != Some(c) {
248 return Err(format!("invalid literal near position {}", self.pos));
249 }
250 }
251 Ok(())
252 }
253
254 fn value(&mut self) -> Result<Json, String> {
255 self.skip_ws();
256 match self.peek() {
257 Some('n') => {
258 self.literal("null")?;
259 Ok(Json::Null)
260 }
261 Some('t') => {
262 self.literal("true")?;
263 Ok(Json::Bool(true))
264 }
265 Some('f') => {
266 self.literal("false")?;
267 Ok(Json::Bool(false))
268 }
269 Some('"') => Ok(Json::String(self.string()?)),
270 Some('[') => self.array(),
271 Some('{') => self.object(),
272 Some(c) if c == '-' || c.is_ascii_digit() => self.number(),
273 Some(c) => Err(format!("unexpected '{c}' at position {}", self.pos)),
274 None => Err("unexpected end of input".to_string()),
275 }
276 }
277
278 fn string(&mut self) -> Result<String, String> {
279 self.expect('"')?;
280 let mut out = String::new();
281 loop {
282 match self.bump() {
283 None => return Err("unterminated string".to_string()),
284 Some('"') => return Ok(out),
285 Some('\\') => match self.bump() {
286 Some('"') => out.push('"'),
287 Some('\\') => out.push('\\'),
288 Some('/') => out.push('/'),
289 Some('b') => out.push('\u{8}'),
290 Some('f') => out.push('\u{c}'),
291 Some('n') => out.push('\n'),
292 Some('r') => out.push('\r'),
293 Some('t') => out.push('\t'),
294 Some('u') => {
295 let mut code = 0u32;
296 for _ in 0..4 {
297 let c = self.bump().ok_or("truncated \\u escape")?;
298 let d = c.to_digit(16).ok_or("invalid \\u escape")?;
299 code = code * 16 + d;
300 }
301 if (0xD800..0xDC00).contains(&code) && self.peek() == Some('\\') {
305 self.pos += 1;
306 self.expect('u')?;
307 let mut low = 0u32;
308 for _ in 0..4 {
309 let c = self.bump().ok_or("truncated \\u escape")?;
310 let d = c.to_digit(16).ok_or("invalid \\u escape")?;
311 low = low * 16 + d;
312 }
313 code = 0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00);
314 }
315 out.push(char::from_u32(code).unwrap_or('\u{FFFD}'));
316 }
317 _ => return Err("invalid escape".to_string()),
318 },
319 Some(c) if (c as u32) < 0x20 => {
320 return Err("unescaped control character in string".to_string())
321 }
322 Some(c) => out.push(c),
323 }
324 }
325 }
326
327 fn number(&mut self) -> Result<Json, String> {
328 let start = self.pos;
329 if self.peek() == Some('-') {
330 self.pos += 1;
331 }
332 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
333 self.pos += 1;
334 }
335 if self.peek() == Some('.') {
336 self.pos += 1;
337 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
338 self.pos += 1;
339 }
340 }
341 if matches!(self.peek(), Some('e' | 'E')) {
342 self.pos += 1;
343 if matches!(self.peek(), Some('+' | '-')) {
344 self.pos += 1;
345 }
346 while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
347 self.pos += 1;
348 }
349 }
350 let text: String = self.input[start..self.pos].iter().collect();
351 text.parse::<f64>()
352 .map(Json::Number)
353 .map_err(|_| format!("invalid number '{text}'"))
354 }
355
356 fn array(&mut self) -> Result<Json, String> {
364 self.depth += 1;
365 if self.depth > MAX_DEPTH {
366 return Err(format!(
367 "nesting deeper than {MAX_DEPTH} at position {}",
368 self.pos
369 ));
370 }
371 let parsed = self.array_inner();
372 self.depth -= 1;
373 parsed
374 }
375
376 fn array_inner(&mut self) -> Result<Json, String> {
377 self.expect('[')?;
378 let mut items = Vec::new();
379 self.skip_ws();
380 if self.peek() == Some(']') {
381 self.pos += 1;
382 return Ok(Json::Array(items));
383 }
384 loop {
385 items.push(self.value()?);
386 self.skip_ws();
387 match self.bump() {
388 Some(',') => continue,
389 Some(']') => return Ok(Json::Array(items)),
390 _ => return Err("expected ',' or ']'".to_string()),
391 }
392 }
393 }
394
395 fn object(&mut self) -> Result<Json, String> {
403 self.depth += 1;
404 if self.depth > MAX_DEPTH {
405 return Err(format!(
406 "nesting deeper than {MAX_DEPTH} at position {}",
407 self.pos
408 ));
409 }
410 let parsed = self.object_inner();
411 self.depth -= 1;
412 parsed
413 }
414
415 fn object_inner(&mut self) -> Result<Json, String> {
416 self.expect('{')?;
417 let mut map = BTreeMap::new();
418 self.skip_ws();
419 if self.peek() == Some('}') {
420 self.pos += 1;
421 return Ok(Json::Object(map));
422 }
423 loop {
424 self.skip_ws();
425 let key = self.string()?;
426 self.skip_ws();
427 self.expect(':')?;
428 let value = self.value()?;
429 map.insert(key, value);
430 self.skip_ws();
431 match self.bump() {
432 Some(',') => continue,
433 Some('}') => return Ok(Json::Object(map)),
434 _ => return Err("expected ',' or '}'".to_string()),
435 }
436 }
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443
444 #[test]
446 fn array_and_number_accessors_report_the_right_shape() {
447 let v = parse(r#"{"xs":[1,2],"empty":[],"n":1.5,"s":"t"}"#).unwrap();
448
449 assert_eq!(
450 v.get("xs").and_then(|x| x.as_array()).map(|a| a.len()),
451 Some(2)
452 );
453 assert_eq!(
455 v.get("empty").and_then(|x| x.as_array()).map(|a| a.len()),
456 Some(0)
457 );
458 assert!(
459 v.get("s").unwrap().as_array().is_none(),
460 "a string is not an array"
461 );
462
463 assert_eq!(v.get("n").and_then(|x| x.as_f64()), Some(1.5));
464 assert_eq!(v.get("n").and_then(|x| x.as_i64()), Some(1));
466 assert!(v.get("s").unwrap().as_f64().is_none());
467 }
468
469 fn alloc_join(n: usize, unit: &str) -> String {
471 vec![unit; n].join(",")
472 }
473
474 fn alloc_join_with(n: usize, unit: &str) -> String {
477 (0..n).map(|_| unit).collect::<Vec<_>>().join(",")
478 }
479
480 #[test]
481 fn parses_scalars() {
482 assert_eq!(parse("null").unwrap(), Json::Null);
483 assert_eq!(parse("true").unwrap(), Json::Bool(true));
484 assert_eq!(parse("false").unwrap(), Json::Bool(false));
485 assert_eq!(parse("42").unwrap(), Json::Number(42.0));
486 assert_eq!(parse("-1.5e2").unwrap(), Json::Number(-150.0));
487 assert_eq!(parse(r#""hi""#).unwrap(), Json::str("hi"));
488 }
489
490 #[test]
491 fn parses_nested_structures() {
492 let v = parse(r#"{"a":[1,2,{"b":null}],"c":"d"}"#).unwrap();
493 assert_eq!(v.get("c").unwrap().as_str(), Some("d"));
494 let a = v.get("a").unwrap();
495 match a {
496 Json::Array(items) => {
497 assert_eq!(items.len(), 3);
498 assert_eq!(items[2].get("b"), Some(&Json::Null));
499 }
500 _ => panic!("expected an array"),
501 }
502 }
503
504 #[test]
505 fn handles_escapes() {
506 assert_eq!(parse(r#""a\nb""#).unwrap(), Json::str("a\nb"));
507 assert_eq!(parse(r#""a\\b""#).unwrap(), Json::str("a\\b"));
508 assert_eq!(parse(r#""a\"b""#).unwrap(), Json::str("a\"b"));
509 assert_eq!(parse(r#""A""#).unwrap(), Json::str("A"));
510 assert_eq!(parse(r#""😀""#).unwrap(), Json::str("\u{1F600}"));
512 }
513
514 #[test]
522 fn deep_nesting_is_refused_rather_than_fatal() {
523 for depth in [MAX_DEPTH + 1, 1_000, 100_000] {
524 let deep = "[".repeat(depth) + &"]".repeat(depth);
525 let err = parse(&deep).expect_err("{depth} deep should be refused");
526 assert!(
527 err.contains("nesting"),
528 "the error should say what was wrong: {err}"
529 );
530
531 let deep = "{\"a\":".repeat(depth) + "1" + &"}".repeat(depth);
532 assert!(
533 parse(&deep).is_err(),
534 "{depth} deep objects should be refused"
535 );
536 }
537 }
538
539 #[test]
544 fn the_limit_is_exactly_where_it_claims() {
545 let at = "[".repeat(MAX_DEPTH) + &"]".repeat(MAX_DEPTH);
546 assert!(parse(&at).is_ok(), "{MAX_DEPTH} deep should parse");
547
548 let over = "[".repeat(MAX_DEPTH + 1) + &"]".repeat(MAX_DEPTH + 1);
549 assert!(parse(&over).is_err(), "{} deep should not", MAX_DEPTH + 1);
550 }
551
552 #[test]
558 fn a_wide_document_is_not_a_deep_one() {
559 let wide = format!("[{}]", vec!["1"; 50_000].join(","));
560 let parsed = parse(&wide).expect("a flat array of 50,000 items is not deep");
561 match parsed {
562 Json::Array(items) => assert_eq!(items.len(), 50_000),
563 other => panic!("expected an array, got {other:?}"),
564 }
565
566 let nested = r#"{"a":{"b":{"c":{"d":{"e":{"f":{"g":[1,2,3]}}}}}}}"#;
569 assert!(parse(nested).is_ok());
570 }
571
572 #[test]
586 fn depth_is_released_between_siblings() {
587 let siblings = alloc_join(MAX_DEPTH * 8, "[]");
590 let doc = format!("[{siblings}]");
591 let parsed = parse(&doc).expect("wide and shallow should parse");
592 match parsed {
593 Json::Array(items) => assert_eq!(items.len(), MAX_DEPTH * 8),
594 other => panic!("expected an array, got {other:?}"),
595 }
596
597 let siblings = alloc_join(MAX_DEPTH * 8, "{}");
599 assert!(parse(&format!("[{siblings}]")).is_ok());
600
601 let branch = "[".repeat(MAX_DEPTH - 2) + &"]".repeat(MAX_DEPTH - 2);
604 let many = alloc_join_with(8, &branch);
605 assert!(
606 parse(&format!("[{many}]")).is_ok(),
607 "deep branches side by side should parse; the counter is not falling"
608 );
609
610 let over = "[".repeat(MAX_DEPTH + 10) + &"]".repeat(MAX_DEPTH + 10);
613 assert!(parse(&over).is_err());
614 assert!(parse(&doc).is_ok(), "a refusal poisoned the next parse");
615 }
616
617 #[test]
618 fn rejects_malformed_input() {
619 for bad in [
620 "",
621 "{",
622 "[1,]",
623 r#"{"a"}"#,
624 r#"{"a":}"#,
625 "tru",
626 "{} {}",
627 "\"unterminated",
628 "01x",
629 ] {
630 assert!(parse(bad).is_err(), "{bad:?} should not parse");
631 }
632 }
633
634 #[test]
635 fn roundtrips_through_serialization() {
636 let cases = [
637 r#"{"a":1,"b":[true,false,null],"c":"x"}"#,
638 r#"[]"#,
639 r#"{}"#,
640 r#"{"nested":{"deep":{"value":-3}}}"#,
641 ];
642 for case in cases {
643 let v = parse(case).unwrap();
644 let text = v.to_string();
645 assert_eq!(parse(&text).unwrap(), v, "round trip of {case}");
646 }
647 }
648
649 #[test]
650 fn serialization_escapes_control_characters() {
651 let v = Json::str("tab\there\u{1}");
652 let text = v.to_string();
653 assert!(text.contains("\\t"));
654 assert!(text.contains("\\u0001"));
655 assert_eq!(parse(&text).unwrap(), v);
656 }
657
658 #[test]
659 fn integers_serialize_without_a_decimal_point() {
660 assert_eq!(Json::num(42.0).to_string(), "42");
661 assert_eq!(Json::num(-7.0).to_string(), "-7");
662 assert_eq!(Json::num(0.5).to_string(), "0.5");
663 }
664
665 #[test]
666 fn object_keys_are_ordered_deterministically() {
667 let a = Json::object([("z", Json::num(1)), ("a", Json::num(2))]);
668 assert_eq!(a.to_string(), r#"{"a":2,"z":1}"#);
669 }
670
671 #[test]
672 fn accessors_return_none_on_type_mismatch() {
673 let v = Json::str("text");
674 assert_eq!(v.as_i64(), None);
675 assert_eq!(v.as_bool(), None);
676 assert_eq!(v.get("key"), None);
677 }
678}