use std::collections::BTreeMap; use std::fmt::{Display, Formatter}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum JsonValue { Null, Bool(bool), Number(i64), String(String), Array(Vec), Object(BTreeMap), } #[derive(Debug, Clone, PartialEq, Eq)] pub struct JsonError { message: String, } impl JsonError { #[must_use] pub fn new(message: impl Into) -> Self { Self { message: message.into(), } } } impl Display for JsonError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.message) } } impl std::error::Error for JsonError {} impl JsonValue { #[must_use] pub fn render(&self) -> String { match self { Self::Null => "null".to_string(), Self::Bool(value) => value.to_string(), Self::Number(value) => value.to_string(), Self::String(value) => render_string(value), Self::Array(values) => { let rendered = values .iter() .map(Self::render) .collect::>() .join(","); format!("[{rendered}]") } Self::Object(entries) => { let rendered = entries .iter() .map(|(key, value)| format!("{}:{}", render_string(key), value.render())) .collect::>() .join(","); format!("{{{rendered}}}") } } } pub fn parse(source: &str) -> Result { let mut parser = Parser::new(source); let value = parser.parse_value()?; parser.skip_whitespace(); if parser.is_eof() { Ok(value) } else { Err(JsonError::new("unexpected trailing content")) } } #[must_use] pub fn as_object(&self) -> Option<&BTreeMap> { match self { Self::Object(value) => Some(value), _ => None, } } #[must_use] pub fn as_array(&self) -> Option<&[JsonValue]> { match self { Self::Array(value) => Some(value), _ => None, } } #[must_use] pub fn as_str(&self) -> Option<&str> { match self { Self::String(value) => Some(value), _ => None, } } #[must_use] pub fn as_bool(&self) -> Option { match self { Self::Bool(value) => Some(*value), _ => None, } } #[must_use] pub fn as_i64(&self) -> Option { match self { Self::Number(value) => Some(*value), _ => None, } } } fn render_string(value: &str) -> String { let mut rendered = String::with_capacity(value.len() + 2); rendered.push('"'); for ch in value.chars() { match ch { '"' => rendered.push_str("\\\""), '\\' => rendered.push_str("\\\\"), '\n' => rendered.push_str("\\n"), '\r' => rendered.push_str("\\r"), '\t' => rendered.push_str("\\t"), '\u{08}' => rendered.push_str("\\b"), '\u{0C}' => rendered.push_str("\\f"), control if control.is_control() => push_unicode_escape(&mut rendered, control), plain => rendered.push(plain), } } rendered.push('"'); rendered } fn push_unicode_escape(rendered: &mut String, control: char) { const HEX: &[u8; 16] = b"0123456789abcdef"; rendered.push_str("\\u"); let value = u32::from(control); for shift in [12_u32, 8, 4, 0] { let nibble = ((value >> shift) & 0xF) as usize; rendered.push(char::from(HEX[nibble])); } } struct Parser<'a> { chars: Vec, index: usize, _source: &'a str, } impl<'a> Parser<'a> { fn new(source: &'a str) -> Self { Self { chars: source.chars().collect(), index: 0, _source: source, } } fn parse_value(&mut self) -> Result { self.skip_whitespace(); match self.peek() { Some('n') => self.parse_literal("null", JsonValue::Null), Some('t') => self.parse_literal("true", JsonValue::Bool(true)), Some('f') => self.parse_literal("false", JsonValue::Bool(false)), Some('"') => self.parse_string().map(JsonValue::String), Some('[') => self.parse_array(), Some('{') => self.parse_object(), Some('-' | '0'..='9') => self.parse_number().map(JsonValue::Number), Some(other) => Err(JsonError::new(format!("unexpected character: {other}"))), None => Err(JsonError::new("unexpected end of input")), } } fn parse_literal(&mut self, expected: &str, value: JsonValue) -> Result { for expected_char in expected.chars() { if self.next() != Some(expected_char) { return Err(JsonError::new(format!( "invalid literal: expected {expected}" ))); } } Ok(value) } fn parse_string(&mut self) -> Result { self.expect('"')?; let mut value = String::new(); while let Some(ch) = self.next() { match ch { '"' => return Ok(value), '\\' => value.push(self.parse_escape()?), plain => value.push(plain), } } Err(JsonError::new("unterminated string")) } fn parse_escape(&mut self) -> Result { match self.next() { Some('"') => Ok('"'), Some('\\') => Ok('\\'), Some('/') => Ok('/'), Some('b') => Ok('\u{08}'), Some('f') => Ok('\u{0C}'), Some('n') => Ok('\n'), Some('r') => Ok('\r'), Some('t') => Ok('\t'), Some('u') => self.parse_unicode_escape(), Some(other) => Err(JsonError::new(format!("invalid escape sequence: {other}"))), None => Err(JsonError::new("unexpected end of input in escape sequence")), } } fn parse_unicode_escape(&mut self) -> Result { let mut value = 0_u32; for _ in 0..4 { let Some(ch) = self.next() else { return Err(JsonError::new("unexpected end of input in unicode escape")); }; value = (value << 4) | ch.to_digit(16) .ok_or_else(|| JsonError::new("invalid unicode escape"))?; } char::from_u32(value).ok_or_else(|| JsonError::new("invalid unicode scalar value")) } fn parse_array(&mut self) -> Result { self.expect('[')?; let mut values = Vec::new(); loop { self.skip_whitespace(); if self.try_consume(']') { break; } values.push(self.parse_value()?); self.skip_whitespace(); if self.try_consume(']') { break; } self.expect(',')?; } Ok(JsonValue::Array(values)) } fn parse_object(&mut self) -> Result { self.expect('{')?; let mut entries = BTreeMap::new(); loop { self.skip_whitespace(); if self.try_consume('}') { break; } let key = self.parse_string()?; self.skip_whitespace(); self.expect(':')?; let value = self.parse_value()?; entries.insert(key, value); self.skip_whitespace(); if self.try_consume('}') { break; } self.expect(',')?; } Ok(JsonValue::Object(entries)) } fn parse_number(&mut self) -> Result { let mut value = String::new(); if self.try_consume('-') { value.push('-'); } while let Some(ch @ '0'..='9') = self.peek() { value.push(ch); self.index += 1; } if value.is_empty() || value == "-" { return Err(JsonError::new("invalid number")); } value .parse::() .map_err(|_| JsonError::new("number out of range")) } fn expect(&mut self, expected: char) -> Result<(), JsonError> { match self.next() { Some(actual) if actual == expected => Ok(()), Some(actual) => Err(JsonError::new(format!( "expected '{expected}', found '{actual}'" ))), None => Err(JsonError::new(format!( "expected '{expected}', found end of input" ))), } } fn try_consume(&mut self, expected: char) -> bool { if self.peek() == Some(expected) { self.index += 1; true } else { false } } fn skip_whitespace(&mut self) { while matches!(self.peek(), Some(' ' | '\n' | '\r' | '\t')) { self.index += 1; } } fn peek(&self) -> Option { self.chars.get(self.index).copied() } fn next(&mut self) -> Option { let ch = self.peek()?; self.index += 1; Some(ch) } fn is_eof(&self) -> bool { self.index >= self.chars.len() } } #[cfg(test)] mod tests { use super::{render_string, JsonValue}; use std::collections::BTreeMap; #[test] fn renders_and_parses_json_values() { let mut object = BTreeMap::new(); object.insert("flag".to_string(), JsonValue::Bool(true)); object.insert( "items".to_string(), JsonValue::Array(vec![ JsonValue::Number(4), JsonValue::String("ok".to_string()), ]), ); let rendered = JsonValue::Object(object).render(); let parsed = JsonValue::parse(&rendered).expect("json should parse"); assert_eq!(parsed.as_object().expect("object").len(), 2); } #[test] fn escapes_control_characters() { assert_eq!(render_string("a\n\t\"b"), "\"a\\n\\t\\\"b\""); } }