use std::io::{self, Read}; #[derive(Debug, PartialEq)] enum Json<'a> { Null, Bool(bool), Number(&'a str), String(&'a str), Array(Vec>), Object(Vec<(&'a str, Json<'a>)>), } struct Parser<'a> { input: &'a str, pos: usize, } impl<'a> Parser<'a> { fn new(input: &'a str) -> Self { Self { input, pos: 0 } } fn parse(mut self) -> Result, String> { let value = self.value()?; self.skip_whitespace(); if self.pos != self.input.len() { return Err(self.error("unexpected trailing input")); } Ok(value) } fn value(&mut self) -> Result, String> { self.skip_whitespace(); match self.peek() { Some(b'n') => { self.keyword("null")?; Ok(Json::Null) } Some(b't') => { self.keyword("true")?; Ok(Json::Bool(true)) } Some(b'f') => { self.keyword("false")?; Ok(Json::Bool(false)) } Some(b'"') => Ok(Json::String(self.string()?)), Some(b'[') => self.array(), Some(b'{') => self.object(), Some(b'-' | b'0'..=b'9') => Ok(Json::Number(self.number()?)), Some(_) => Err(self.error("expected a JSON value")), None => Err(self.error("unexpected end of input")), } } fn array(&mut self) -> Result, String> { self.expect(b'[')?; self.skip_whitespace(); let mut values = Vec::new(); if self.consume(b']') { return Ok(Json::Array(values)); } loop { values.push(self.value()?); self.skip_whitespace(); if self.consume(b']') { break; } self.expect(b',')?; } Ok(Json::Array(values)) } fn object(&mut self) -> Result, String> { self.expect(b'{')?; self.skip_whitespace(); let mut fields = Vec::new(); if self.consume(b'}') { return Ok(Json::Object(fields)); } loop { self.skip_whitespace(); let key = self.string()?; self.skip_whitespace(); self.expect(b':')?; let value = self.value()?; fields.push((key, value)); self.skip_whitespace(); if self.consume(b'}') { break; } self.expect(b',')?; } Ok(Json::Object(fields)) } // Keep strings in their original escaped form, including their quotes. fn string(&mut self) -> Result<&'a str, String> { let start = self.pos; self.expect(b'"')?; loop { let byte = self.next().ok_or_else(|| self.error("unterminated string"))?; match byte { b'"' => return Ok(&self.input[start..self.pos]), b'\\' => match self.next() { Some(b'"' | b'\\' | b'/' | b'b' | b'f' | b'n' | b'r' | b't') => {} Some(b'u') => { for _ in 0..4 { match self.next() { Some(b'0'..=b'9' | b'a'..=b'f' | b'A'..=b'F') => {} _ => return Err(self.error("invalid Unicode escape")), } } } _ => return Err(self.error("invalid string escape")), }, 0x00..=0x1f => return Err(self.error("control character in string")), _ => {} } } } fn number(&mut self) -> Result<&'a str, String> { let start = self.pos; self.consume(b'-'); if self.consume(b'0') { if matches!(self.peek(), Some(b'0'..=b'9')) { return Err(self.error("leading zero in number")); } } else { self.digits(true)?; } if self.consume(b'.') { self.digits(true)?; } if matches!(self.peek(), Some(b'e' | b'E')) { self.pos += 1; if matches!(self.peek(), Some(b'+' | b'-')) { self.pos += 1; } self.digits(true)?; } Ok(&self.input[start..self.pos]) } fn digits(&mut self, require_one: bool) -> Result<(), String> { let start = self.pos; while matches!(self.peek(), Some(b'0'..=b'9')) { self.pos += 1; } if require_one && self.pos == start { Err(self.error("expected a digit")) } else { Ok(()) } } fn keyword(&mut self, word: &str) -> Result<(), String> { if self.input[self.pos..].starts_with(word) { self.pos += word.len(); Ok(()) } else { Err(self.error("invalid keyword")) } } fn skip_whitespace(&mut self) { while matches!(self.peek(), Some(b' ' | b'\n' | b'\r' | b'\t')) { self.pos += 1; } } fn expect(&mut self, expected: u8) -> Result<(), String> { if self.consume(expected) { Ok(()) } else { Err(self.error(&format!("expected '{}'", expected as char))) } } fn consume(&mut self, expected: u8) -> bool { if self.peek() == Some(expected) { self.pos += 1; true } else { false } } fn peek(&self) -> Option { self.input.as_bytes().get(self.pos).copied() } fn next(&mut self) -> Option { let byte = self.peek()?; self.pos += 1; Some(byte) } fn error(&self, message: &str) -> String { format!("{message} at byte {}", self.pos) } } fn write_pretty(value: &Json<'_>, output: &mut String, depth: usize) { match value { Json::Null => output.push_str("null"), Json::Bool(value) => output.push_str(if *value { "true" } else { "false" }), Json::Number(number) | Json::String(number) => output.push_str(number), Json::Array(values) => { output.push('['); if !values.is_empty() { output.push('\n'); for (index, value) in values.iter().enumerate() { indent(output, depth + 1); write_pretty(value, output, depth + 1); if index + 1 != values.len() { output.push(','); } output.push('\n'); } indent(output, depth); } output.push(']'); } Json::Object(fields) => { output.push('{'); if !fields.is_empty() { output.push('\n'); for (index, (key, value)) in fields.iter().enumerate() { indent(output, depth + 1); output.push_str(key); output.push_str(": "); write_pretty(value, output, depth + 1); if index + 1 != fields.len() { output.push(','); } output.push('\n'); } indent(output, depth); } output.push('}'); } } } fn indent(output: &mut String, depth: usize) { for _ in 0..depth { output.push_str(" "); } } fn pretty_print(input: &str) -> Result { let value = Parser::new(input).parse()?; let mut output = String::new(); write_pretty(&value, &mut output, 0); Ok(output) } fn main() { let mut input = String::new(); if let Err(error) = io::stdin().read_to_string(&mut input) { eprintln!("failed to read input: {error}"); std::process::exit(1); } match pretty_print(&input) { Ok(output) => println!("{output}"), Err(error) => { eprintln!("invalid JSON: {error}"); std::process::exit(1); } } } #[cfg(test)] mod tests { use super::pretty_print; #[test] fn formats_nested_json() { let input = r#"{"name":"Ada","active":true,"items":[1,null,{"x":2}]}"#; let expected = r#"{ "name": "Ada", "active": true, "items": [ 1, null, { "x": 2 } ] }"#; assert_eq!(pretty_print(input).unwrap(), expected); } #[test] fn rejects_invalid_json() { assert!(pretty_print(r#"{"missing": [1, 2,]}"#).is_err()); assert!(pretty_print("01").is_err()); } }