| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- 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<JsonValue>),
- Object(BTreeMap<String, JsonValue>),
- }
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct JsonError {
- message: String,
- }
- impl JsonError {
- #[must_use]
- pub fn new(message: impl Into<String>) -> 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::<Vec<_>>()
- .join(",");
- format!("[{rendered}]")
- }
- Self::Object(entries) => {
- let rendered = entries
- .iter()
- .map(|(key, value)| format!("{}:{}", render_string(key), value.render()))
- .collect::<Vec<_>>()
- .join(",");
- format!("{{{rendered}}}")
- }
- }
- }
- pub fn parse(source: &str) -> Result<Self, JsonError> {
- 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<String, JsonValue>> {
- 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<bool> {
- match self {
- Self::Bool(value) => Some(*value),
- _ => None,
- }
- }
- #[must_use]
- pub fn as_i64(&self) -> Option<i64> {
- 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<char>,
- 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<JsonValue, JsonError> {
- 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<JsonValue, JsonError> {
- 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<String, JsonError> {
- 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<char, JsonError> {
- 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<char, JsonError> {
- 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<JsonValue, JsonError> {
- 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<JsonValue, JsonError> {
- 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<i64, JsonError> {
- 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::<i64>()
- .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<char> {
- self.chars.get(self.index).copied()
- }
- fn next(&mut self) -> Option<char> {
- 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\"");
- }
- }
|