1#![forbid(unsafe_code)]
38#![deny(missing_docs)]
39
40use std::collections::BTreeMap;
41use std::fmt::Write as _;
42
43#[derive(Debug, Clone, PartialEq)]
45pub enum Json {
46 Null,
48 Bool(bool),
50 Number(f64),
52 String(String),
54 Array(Vec<Json>),
56 Object(BTreeMap<String, Json>),
58}
59
60impl Json {
61 #[must_use]
63 pub fn object(pairs: Vec<(&str, Self)>) -> Self {
64 Self::Object(
65 pairs.into_iter().map(|(k, v)| (k.to_owned(), v)).collect(),
66 )
67 }
68
69 #[must_use]
71 pub fn str(s: impl Into<String>) -> Self {
72 Self::String(s.into())
73 }
74
75 #[must_use]
77 pub fn get(&self, key: &str) -> Option<&Self> {
78 match self {
79 Self::Object(m) => m.get(key),
80 _ => None,
81 }
82 }
83
84 #[must_use]
86 pub fn as_str(&self) -> Option<&str> {
87 match self {
88 Self::String(s) => Some(s),
89 _ => None,
90 }
91 }
92
93 #[must_use]
95 pub fn to_json(&self) -> String {
96 let mut out = String::new();
97 self.write(&mut out);
98 out
99 }
100
101 fn write(&self, out: &mut String) {
102 match self {
103 Self::Null => out.push_str("null"),
104 Self::Bool(b) => {
105 out.push_str(if *b { "true" } else { "false" });
106 }
107 Self::Number(n) => {
108 #[allow(clippy::cast_possible_truncation)]
112 if n.fract() == 0.0 && n.abs() < 1e15 {
113 let _ = write!(out, "{}", *n as i64);
114 } else {
115 let _ = write!(out, "{n}");
116 }
117 }
118 Self::String(s) => write_string(s, out),
119 Self::Array(items) => {
120 out.push('[');
121 for (i, item) in items.iter().enumerate() {
122 if i > 0 {
123 out.push(',');
124 }
125 item.write(out);
126 }
127 out.push(']');
128 }
129 Self::Object(map) => {
130 out.push('{');
131 for (i, (k, v)) in map.iter().enumerate() {
132 if i > 0 {
133 out.push(',');
134 }
135 write_string(k, out);
136 out.push(':');
137 v.write(out);
138 }
139 out.push('}');
140 }
141 }
142 }
143}
144
145fn write_string(s: &str, out: &mut String) {
146 out.push('"');
147 for c in s.chars() {
148 match c {
149 '"' => out.push_str("\\\""),
150 '\\' => out.push_str("\\\\"),
151 '\n' => out.push_str("\\n"),
152 '\r' => out.push_str("\\r"),
153 '\t' => out.push_str("\\t"),
154 c if (c as u32) < 0x20 => {
157 let _ = write!(out, "\\u{:04x}", c as u32);
158 }
159 c => out.push(c),
160 }
161 }
162 out.push('"');
163}
164
165pub fn parse(input: &str) -> Result<Json, String> {
171 let chars: Vec<char> = input.chars().collect();
172 let mut p = Parser {
173 chars: &chars,
174 pos: 0,
175 };
176 p.ws();
177 let v = p.value()?;
178 p.ws();
179 if p.pos < chars.len() {
180 return Err("trailing input after JSON value".to_owned());
181 }
182 Ok(v)
183}
184
185struct Parser<'a> {
186 chars: &'a [char],
187 pos: usize,
188}
189
190impl Parser<'_> {
191 fn ws(&mut self) {
192 while matches!(self.chars.get(self.pos), Some(' ' | '\t' | '\n' | '\r'))
193 {
194 self.pos += 1;
195 }
196 }
197
198 fn value(&mut self) -> Result<Json, String> {
199 self.ws();
200 match self.chars.get(self.pos) {
201 Some('{') => self.object(),
202 Some('[') => self.array(),
203 Some('"') => Ok(Json::String(self.string()?)),
204 Some('t') => self.literal("true", Json::Bool(true)),
205 Some('f') => self.literal("false", Json::Bool(false)),
206 Some('n') => self.literal("null", Json::Null),
207 Some(_) => self.number(),
208 None => Err("unexpected end of JSON".to_owned()),
209 }
210 }
211
212 fn literal(&mut self, word: &str, v: Json) -> Result<Json, String> {
213 if self.chars[self.pos..]
214 .starts_with(word.chars().collect::<Vec<_>>().as_slice())
215 {
216 self.pos += word.len();
217 Ok(v)
218 } else {
219 Err(format!("expected `{word}`"))
220 }
221 }
222
223 fn object(&mut self) -> Result<Json, String> {
224 self.pos += 1;
225 let mut map = BTreeMap::new();
226 self.ws();
227 if self.chars.get(self.pos) == Some(&'}') {
228 self.pos += 1;
229 return Ok(Json::Object(map));
230 }
231 loop {
232 self.ws();
233 let key = self.string()?;
234 self.ws();
235 if self.chars.get(self.pos) != Some(&':') {
236 return Err("expected `:` in object".to_owned());
237 }
238 self.pos += 1;
239 let value = self.value()?;
240 let _ = map.insert(key, value);
241 self.ws();
242 match self.chars.get(self.pos) {
243 Some(',') => self.pos += 1,
244 Some('}') => {
245 self.pos += 1;
246 return Ok(Json::Object(map));
247 }
248 _ => return Err("expected `,` or `}`".to_owned()),
249 }
250 }
251 }
252
253 fn array(&mut self) -> Result<Json, String> {
254 self.pos += 1;
255 let mut items = Vec::new();
256 self.ws();
257 if self.chars.get(self.pos) == Some(&']') {
258 self.pos += 1;
259 return Ok(Json::Array(items));
260 }
261 loop {
262 items.push(self.value()?);
263 self.ws();
264 match self.chars.get(self.pos) {
265 Some(',') => self.pos += 1,
266 Some(']') => {
267 self.pos += 1;
268 return Ok(Json::Array(items));
269 }
270 _ => return Err("expected `,` or `]`".to_owned()),
271 }
272 }
273 }
274
275 fn string(&mut self) -> Result<String, String> {
276 if self.chars.get(self.pos) != Some(&'"') {
277 return Err("expected a string".to_owned());
278 }
279 self.pos += 1;
280 let mut out = String::new();
281 loop {
282 match self.chars.get(self.pos) {
283 None => return Err("unterminated string".to_owned()),
284 Some('"') => {
285 self.pos += 1;
286 return Ok(out);
287 }
288 Some('\\') => {
289 self.pos += 1;
290 let esc =
291 self.chars.get(self.pos).ok_or("trailing backslash")?;
292 self.pos += 1;
293 match esc {
294 'n' => out.push('\n'),
295 't' => out.push('\t'),
296 'r' => out.push('\r'),
297 'b' => out.push('\u{8}'),
298 'f' => out.push('\u{c}'),
299 'u' => {
300 let cp = self.hex4()?;
301 let cp = if (0xD800..0xDC00).contains(&cp) {
307 if !matches!(
308 self.chars.get(self.pos..self.pos + 2),
309 Some(['\\', 'u'])
310 ) {
311 return Err(
312 "lone high surrogate".to_owned()
313 );
314 }
315 self.pos += 2;
316 let lo = self.hex4()?;
317 if !(0xDC00..0xE000).contains(&lo) {
318 return Err(
319 "high surrogate not followed by a \
320 low surrogate"
321 .to_owned(),
322 );
323 }
324 0x1_0000 + ((cp - 0xD800) << 10) + (lo - 0xDC00)
325 } else if (0xDC00..0xE000).contains(&cp) {
326 return Err("lone low surrogate".to_owned());
327 } else {
328 cp
329 };
330 out.push(
331 char::from_u32(cp).ok_or("bad code point")?,
332 );
333 }
334 c => out.push(*c),
335 }
336 }
337 Some(c) => {
338 out.push(*c);
339 self.pos += 1;
340 }
341 }
342 }
343 }
344
345 fn hex4(&mut self) -> Result<u32, String> {
347 let hex: String = self
348 .chars
349 .get(self.pos..self.pos + 4)
350 .ok_or("truncated \\u escape")?
351 .iter()
352 .collect();
353 self.pos += 4;
354 u32::from_str_radix(&hex, 16).map_err(|_| "bad \\u escape".to_owned())
355 }
356
357 fn number(&mut self) -> Result<Json, String> {
358 let start = self.pos;
359 while matches!(
360 self.chars.get(self.pos),
361 Some(c) if c.is_ascii_digit()
362 || matches!(c, '-' | '+' | '.' | 'e' | 'E')
363 ) {
364 self.pos += 1;
365 }
366 let text: String = self.chars[start..self.pos].iter().collect();
367 let value: f64 = text
368 .parse()
369 .map_err(|_| format!("invalid number `{text}`"))?;
370 if !value.is_finite() {
380 return Err(format!("number `{text}` is out of range for f64"));
381 }
382 Ok(Json::Number(value))
383 }
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389
390 fn round_trip(text: &str) -> String {
391 parse(text).expect("valid JSON").to_json()
392 }
393
394 #[test]
395 fn a_number_too_large_for_f64_is_refused() {
396 for text in ["1e999", "-3e805", "1e309"] {
404 let err = parse(text).expect_err("should be refused");
405 assert!(
406 err.contains("out of range"),
407 "{text} gave the wrong error: {err}"
408 );
409 }
410 assert!(parse("1.7976931348623157e308").is_ok());
413 }
414
415 #[test]
416 fn scalars_round_trip() {
417 assert_eq!(round_trip("null"), "null");
418 assert_eq!(round_trip("true"), "true");
419 assert_eq!(round_trip("false"), "false");
420 assert_eq!(round_trip("\"hi\""), "\"hi\"");
421 }
422
423 #[test]
424 fn integral_numbers_print_without_a_fraction() {
425 assert_eq!(round_trip("2"), "2");
428 assert_eq!(round_trip("-17"), "-17");
429 assert_eq!(round_trip("0"), "0");
430 assert_eq!(round_trip("1.5"), "1.5");
431 assert_eq!(round_trip("1e3"), "1000");
432 }
433
434 #[test]
435 fn very_large_integral_numbers_do_not_wrap() {
436 let out = Json::Number(1e300).to_json();
439 assert!(out.starts_with('1'), "{out}");
440 assert!(parse(&out).is_ok(), "{out} did not re-parse");
441 }
442
443 #[test]
444 fn objects_serialise_in_a_stable_order() {
445 let text = round_trip(r#"{"b":1,"a":2}"#);
446 assert_eq!(text, r#"{"a":2,"b":1}"#);
447 }
448
449 #[test]
450 fn nesting_round_trips() {
451 let src = r#"{"a":[1,[2,{"b":null}]],"c":{}}"#;
452 assert_eq!(round_trip(src), src);
453 }
454
455 #[test]
456 fn string_escapes_decode() {
457 let v = parse(r#""a\nb\tc\"d\\e\/f\r\b\f""#).expect("valid");
458 assert_eq!(v.as_str(), Some("a\nb\tc\"d\\e/f\r\u{8}\u{c}"));
459 }
460
461 #[test]
462 fn control_characters_are_escaped_on_output() {
463 let out = Json::str("a\u{1}b\u{1f}").to_json();
466 assert_eq!(out, r#""a\u0001b\u001f""#);
467 assert_eq!(
468 parse(&out).expect("re-parses").as_str(),
469 Some("a\u{1}b\u{1f}")
470 );
471 }
472
473 #[test]
474 fn surrogate_pairs_decode_to_one_character() {
475 assert_eq!(
478 parse(r#""\ud83d\ude00""#).expect("valid").as_str(),
479 Some("\u{1f600}")
480 );
481 assert_eq!(
482 parse(r#""a\ud83d\ude00b""#).expect("valid").as_str(),
483 Some("a\u{1f600}b")
484 );
485 }
486
487 #[test]
488 fn bmp_escapes_still_decode() {
489 assert_eq!(
490 parse(r#""\u00e9\u4e2d""#).expect("valid").as_str(),
491 Some("é中")
492 );
493 }
494
495 #[test]
496 fn lone_surrogates_are_rejected() {
497 assert!(parse(r#""\ud83d""#).is_err());
499 assert!(parse(r#""\ude00""#).is_err());
500 assert!(parse(r#""\ud83dx""#).is_err());
501 assert!(parse(r#""\ud83d\u0041""#).is_err());
502 }
503
504 #[test]
505 fn raw_utf8_passes_through_unescaped() {
506 let src = "\"héllo 😀 中\"";
507 assert_eq!(round_trip(src), src);
508 }
509
510 #[test]
511 fn malformed_input_is_an_error_not_a_panic() {
512 for bad in [
513 "",
514 "{",
515 "[",
516 "{\"a\"}",
517 "{\"a\":}",
518 "[1,",
519 "\"unterminated",
520 "tru",
521 "nul",
522 "\"\\",
523 "\"\\u00\"",
524 "\"\\uZZZZ\"",
525 "@",
526 "{\"a\":1}trailing",
527 "1 2",
528 ] {
529 assert!(parse(bad).is_err(), "`{bad}` should not parse");
530 }
531 }
532
533 #[test]
534 fn whitespace_around_values_is_ignored() {
535 assert_eq!(
536 round_trip(" \n\t{ \"a\" : [ 1 , 2 ] }\r\n"),
537 r#"{"a":[1,2]}"#
538 );
539 }
540
541 #[test]
542 fn get_and_as_str_are_type_safe() {
543 let v = parse(r#"{"a":"x","b":1}"#).expect("valid");
544 assert_eq!(v.get("a").and_then(Json::as_str), Some("x"));
545 assert_eq!(v.get("b").and_then(Json::as_str), None);
546 assert!(v.get("missing").is_none());
547 assert!(Json::Null.get("a").is_none());
548 assert!(Json::Null.as_str().is_none());
549 }
550
551 #[test]
552 fn object_builder_matches_parsed_form() {
553 let built =
554 Json::object(vec![("a", Json::str("x")), ("b", Json::Number(1.0))]);
555 assert_eq!(built.to_json(), r#"{"a":"x","b":1}"#);
556 }
557
558 #[test]
559 fn deep_nesting_does_not_overflow_the_stack() {
560 let depth = 200;
561 let src = format!("{}{}", "[".repeat(depth), "]".repeat(depth));
562 assert!(parse(&src).is_ok());
563 }
564}
565
566#[cfg(test)]
567mod escape_tests {
568 use super::*;
569
570 #[test]
571 fn every_escape_survives_a_round_trip() {
572 for s in [
576 "back\\slash",
577 "carriage\rreturn",
578 "tab\there",
579 "quote\"inside",
580 "new\nline",
581 "all\\ \" \n \r \t together",
582 ] {
583 let encoded = Json::str(s).to_json();
584 assert_eq!(
585 parse(&encoded).expect("re-parses").as_str(),
586 Some(s),
587 "round trip failed for {s:?} via {encoded}"
588 );
589 }
590 }
591
592 #[test]
593 fn a_structural_error_names_what_was_expected() {
594 let e = parse(r#"{"a":1 "b":2}"#).expect_err("malformed");
596 assert!(e.contains(',') || e.contains('}'), "{e}");
597
598 let e = parse("[1 2]").expect_err("malformed");
599 assert!(e.contains(',') || e.contains(']'), "{e}");
600 }
601}