343 lines
11 KiB
Rust
343 lines
11 KiB
Rust
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<JsonValue>),
|
|
Object(BTreeMap<String, JsonValue>),
|
|
}
|
|
#[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<i32> {
|
|
match self {
|
|
JsonValue::Number(value) => Some(*value as i32),
|
|
_ => None,
|
|
}
|
|
}
|
|
pub fn as_bool(&self) -> Option<bool> {
|
|
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<Item = (&'static str, JsonValue)>) -> 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<JsonValue, JsonError> {
|
|
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<i32> for JsonValue {
|
|
fn from(value: i32) -> Self {
|
|
JsonValue::Number(value as f64)
|
|
}
|
|
}
|
|
impl From<bool> 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<String> 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<u8, JsonError> {
|
|
self.bytes
|
|
.get(self.offset)
|
|
.copied()
|
|
.ok_or(JsonError::UnexpectedEnd(self.offset))
|
|
}
|
|
fn bump(&mut self) -> Result<u8, JsonError> {
|
|
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<JsonValue, JsonError> {
|
|
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<JsonValue, JsonError> {
|
|
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<JsonValue, JsonError> {
|
|
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::<f64>().ok())
|
|
.map(JsonValue::Number)
|
|
.ok_or(JsonError::InvalidNumber(start))
|
|
}
|
|
fn parse_string(&mut self) -> Result<String, JsonError> {
|
|
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<JsonValue, JsonError> {
|
|
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<JsonValue, JsonError> {
|
|
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))
|
|
}
|
|
}
|