use std::collections::BTreeMap; use std::fmt::Write as _; #[derive(Debug, Clone, PartialEq)] pub enum JsonValue { Null, Bool(bool), Number(f64), String(String), Array(Vec), Object(BTreeMap), } #[derive(Debug, thiserror::Error)] pub enum JsonError { #[error("unexpected end of input at offset {0}")] UnexpectedEnd(usize), #[error("unexpected byte {byte:?} at offset {offset}")] Unexpected { byte: char, offset: usize }, #[error("invalid number at offset {0}")] InvalidNumber(usize), #[error("invalid escape sequence at offset {0}")] InvalidEscape(usize), } impl JsonValue { pub fn as_i32(&self) -> Option { match self { JsonValue::Number(value) => Some(*value as i32), _ => None, } } pub fn as_bool(&self) -> Option { match self { JsonValue::Bool(value) => Some(*value), _ => None, } } pub fn as_str(&self) -> Option<&str> { match self { JsonValue::String(value) => Some(value), _ => None, } } pub fn get(&self, key: &str) -> Option<&JsonValue> { match self { JsonValue::Object(map) => map.get(key), _ => None, } } pub fn int_or(&self, key: &str, fallback: i32) -> i32 { self.get(key) .and_then(JsonValue::as_i32) .unwrap_or(fallback) } pub fn bool_or(&self, key: &str, fallback: bool) -> bool { self.get(key) .and_then(JsonValue::as_bool) .unwrap_or(fallback) } pub fn str_or<'a>(&'a self, key: &str, fallback: &'a str) -> &'a str { self.get(key) .and_then(JsonValue::as_str) .unwrap_or(fallback) } pub fn object(pairs: impl IntoIterator) -> Self { JsonValue::Object( pairs .into_iter() .map(|(key, value)| (key.to_owned(), value)) .collect(), ) } fn write_into(&self, out: &mut String) { match self { JsonValue::Null => out.push_str("null"), JsonValue::Bool(true) => out.push_str("true"), JsonValue::Bool(false) => out.push_str("false"), JsonValue::Number(value) => { if value.fract() == 0.0 && value.is_finite() { let _ = write!(out, "{}", *value as i64); } else { let _ = write!(out, "{value}"); } } JsonValue::String(value) => write_escaped(out, value), JsonValue::Array(items) => { out.push('['); for (index, item) in items.iter().enumerate() { if index > 0 { out.push(','); } item.write_into(out); } out.push(']'); } JsonValue::Object(map) => { out.push('{'); for (index, (key, value)) in map.iter().enumerate() { if index > 0 { out.push(','); } write_escaped(out, key); out.push(':'); value.write_into(out); } out.push('}'); } } } pub fn parse(input: &str) -> Result { let mut parser = Parser { bytes: input.as_bytes(), offset: 0, }; parser.skip_whitespace(); let value = parser.parse_value()?; parser.skip_whitespace(); Ok(value) } } impl std::fmt::Display for JsonValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut out = String::new(); self.write_into(&mut out); f.write_str(&out) } } impl From for JsonValue { fn from(value: i32) -> Self { JsonValue::Number(value as f64) } } impl From for JsonValue { fn from(value: bool) -> Self { JsonValue::Bool(value) } } impl From<&str> for JsonValue { fn from(value: &str) -> Self { JsonValue::String(value.to_owned()) } } impl From for JsonValue { fn from(value: String) -> Self { JsonValue::String(value) } } fn write_escaped(out: &mut String, value: &str) { out.push('"'); for ch in value.chars() { match ch { '"' => out.push_str("\\\""), '\\' => out.push_str("\\\\"), '\n' => out.push_str("\\n"), '\r' => out.push_str("\\r"), '\t' => out.push_str("\\t"), ch if (ch as u32) < 0x20 => { let _ = write!(out, "\\u{:04x}", ch as u32); } ch => out.push(ch), } } out.push('"'); } struct Parser<'a> { bytes: &'a [u8], offset: usize, } impl<'a> Parser<'a> { fn peek(&self) -> Result { self.bytes .get(self.offset) .copied() .ok_or(JsonError::UnexpectedEnd(self.offset)) } fn bump(&mut self) -> Result { let byte = self.peek()?; self.offset += 1; Ok(byte) } fn skip_whitespace(&mut self) { while let Some(byte) = self.bytes.get(self.offset) { if matches!(byte, b' ' | b'\t' | b'\n' | b'\r') { self.offset += 1; } else { break; } } } fn expect(&mut self, expected: u8) -> Result<(), JsonError> { let byte = self.bump()?; if byte != expected { return Err(JsonError::Unexpected { byte: byte as char, offset: self.offset - 1, }); } Ok(()) } fn parse_value(&mut self) -> Result { self.skip_whitespace(); match self.peek()? { b'{' => self.parse_object(), b'[' => self.parse_array(), b'"' => Ok(JsonValue::String(self.parse_string()?)), b't' => self.parse_literal("true", JsonValue::Bool(true)), b'f' => self.parse_literal("false", JsonValue::Bool(false)), b'n' => self.parse_literal("null", JsonValue::Null), _ => self.parse_number(), } } fn parse_literal(&mut self, literal: &str, value: JsonValue) -> Result { if self.bytes[self.offset..].starts_with(literal.as_bytes()) { self.offset += literal.len(); Ok(value) } else { Err(JsonError::Unexpected { byte: self.peek()? as char, offset: self.offset, }) } } fn parse_number(&mut self) -> Result { let start = self.offset; while let Some(byte) = self.bytes.get(self.offset) { if matches!(byte, b'-' | b'+' | b'.' | b'e' | b'E') || byte.is_ascii_digit() { self.offset += 1; } else { break; } } std::str::from_utf8(&self.bytes[start..self.offset]) .ok() .and_then(|text| text.parse::().ok()) .map(JsonValue::Number) .ok_or(JsonError::InvalidNumber(start)) } fn parse_string(&mut self) -> Result { self.expect(b'"')?; let mut out = String::new(); loop { let byte = self.bump()?; match byte { b'"' => break, b'\\' => { let escape = self.bump()?; match escape { b'"' => out.push('"'), b'\\' => out.push('\\'), b'/' => out.push('/'), b'b' => out.push('\u{8}'), b'f' => out.push('\u{c}'), b'n' => out.push('\n'), b'r' => out.push('\r'), b't' => out.push('\t'), b'u' => { let hex = self .bytes .get(self.offset..self.offset + 4) .ok_or(JsonError::UnexpectedEnd(self.offset))?; let code = std::str::from_utf8(hex) .ok() .and_then(|text| u32::from_str_radix(text, 16).ok()) .ok_or(JsonError::InvalidEscape(self.offset))?; self.offset += 4; out.push( char::from_u32(code) .ok_or(JsonError::InvalidEscape(self.offset))?, ); } _ => return Err(JsonError::InvalidEscape(self.offset - 1)), } } _ => { let start = self.offset - 1; let mut end = self.offset; while end < self.bytes.len() && self.bytes[end] != b'"' && self.bytes[end] != b'\\' { end += 1; } let chunk = std::str::from_utf8(&self.bytes[start..end]) .map_err(|_| JsonError::InvalidEscape(start))?; out.push_str(chunk); self.offset = end; } } } Ok(out) } fn parse_array(&mut self) -> Result { self.expect(b'[')?; let mut items = Vec::new(); self.skip_whitespace(); if self.peek()? == b']' { self.offset += 1; return Ok(JsonValue::Array(items)); } loop { items.push(self.parse_value()?); self.skip_whitespace(); match self.bump()? { b',' => continue, b']' => break, byte => { return Err(JsonError::Unexpected { byte: byte as char, offset: self.offset - 1, }) } } } Ok(JsonValue::Array(items)) } fn parse_object(&mut self) -> Result { self.expect(b'{')?; let mut map = BTreeMap::new(); self.skip_whitespace(); if self.peek()? == b'}' { self.offset += 1; return Ok(JsonValue::Object(map)); } loop { self.skip_whitespace(); let key = self.parse_string()?; self.skip_whitespace(); self.expect(b':')?; let value = self.parse_value()?; map.insert(key, value); self.skip_whitespace(); match self.bump()? { b',' => continue, b'}' => break, byte => { return Err(JsonError::Unexpected { byte: byte as char, offset: self.offset - 1, }) } } } Ok(JsonValue::Object(map)) } }