#[derive(Debug, Clone, PartialEq)]
pub enum Json {
Null,
Bool(bool),
Int(i64),
Real(f64),
Text(String),
Array(Vec<Json>),
Object(Vec<(String, Json)>),
}
impl Json {
pub fn get(&self, key: &str) -> Option<&Json> {
match self {
Json::Object(pairs) => pairs
.iter()
.find(|(name, _)| name == key)
.map(|(_, value)| value),
_ => None,
}
}
pub fn text(&self) -> Option<&str> {
match self {
Json::Text(text) => Some(text),
_ => None,
}
}
pub fn integer(&self) -> Option<i64> {
match self {
Json::Int(number) => Some(*number),
Json::Real(number) if number.fract() == 0.0 => Some(*number as i64),
_ => None,
}
}
pub fn boolean(&self) -> Option<bool> {
match self {
Json::Bool(value) => Some(*value),
_ => None,
}
}
pub fn array(&self) -> Option<&[Json]> {
match self {
Json::Array(items) => Some(items),
_ => None,
}
}
pub fn write(&self) -> String {
let mut out = String::new();
self.write_into(&mut out);
out
}
fn write_into(&self, out: &mut String) {
match self {
Json::Null => out.push_str("null"),
Json::Bool(true) => out.push_str("true"),
Json::Bool(false) => out.push_str("false"),
Json::Int(number) => out.push_str(&number.to_string()),
Json::Real(number) => out.push_str(&real(*number)),
Json::Text(text) => {
out.push('"');
out.push_str(&escape(text));
out.push('"');
}
Json::Array(items) => {
out.push('[');
for (nth, item) in items.iter().enumerate() {
if nth > 0 {
out.push(',');
}
item.write_into(out);
}
out.push(']');
}
Json::Object(pairs) => {
out.push('{');
for (nth, (name, value)) in pairs.iter().enumerate() {
if nth > 0 {
out.push(',');
}
out.push('"');
out.push_str(&escape(name));
out.push_str("\":");
value.write_into(out);
}
out.push('}');
}
}
}
pub fn pretty(&self, depth: usize) -> String {
let pad = " ".repeat(depth + 1);
let close = " ".repeat(depth);
match self {
Json::Array(items) if !items.is_empty() => {
let inner: Vec<String> = items
.iter()
.map(|item| format!("{pad}{}", item.pretty(depth + 1)))
.collect();
format!("[\n{}\n{close}]", inner.join(",\n"))
}
Json::Object(pairs) if !pairs.is_empty() => {
let inner: Vec<String> = pairs
.iter()
.map(|(name, value)| {
format!("{pad}\"{}\": {}", escape(name), value.pretty(depth + 1))
})
.collect();
format!("{{\n{}\n{close}}}", inner.join(",\n"))
}
other => other.write(),
}
}
}
fn real(number: f64) -> String {
if number.is_finite() {
let rendered = format!("{number}");
if rendered.contains(['.', 'e', 'E']) {
rendered
} else {
format!("{rendered}.0")
}
} else {
"null".to_string()
}
}
pub fn escape(text: &str) -> String {
inillucent_base::json::escape(text)
}
pub fn object(pairs: Vec<(&str, Json)>) -> Json {
Json::Object(
pairs
.into_iter()
.map(|(name, value)| (name.to_string(), value))
.collect(),
)
}
pub fn text(text: impl Into<String>) -> Json {
Json::Text(text.into())
}
pub fn parse(source: &str) -> Result<Json, String> {
let characters: Vec<char> = source.chars().collect();
let mut reader = Reader {
characters: &characters,
at: 0,
depth: 0,
};
reader.skip_space();
let value = reader.value()?;
reader.skip_space();
if reader.at < reader.characters.len() {
return Err(format!("trailing input at character {}", reader.at));
}
Ok(value)
}
struct Reader<'a> {
characters: &'a [char],
at: usize,
depth: usize,
}
const MAX_DEPTH: usize = 1000;
impl Reader<'_> {
fn peek(&self) -> Option<char> {
self.characters.get(self.at).copied()
}
fn next(&mut self) -> Option<char> {
let character = self.peek();
if character.is_some() {
self.at += 1;
}
character
}
fn skip_space(&mut self) {
while matches!(self.peek(), Some(character) if character.is_whitespace()) {
self.at += 1;
}
}
fn expect(&mut self, wanted: char) -> Result<(), String> {
match self.next() {
Some(character) if character == wanted => Ok(()),
Some(character) => Err(format!(
"expected '{wanted}' at character {}, found '{character}'",
self.at - 1
)),
None => Err(format!("expected '{wanted}', found end of input")),
}
}
fn descend(&mut self) -> Result<(), String> {
if self.depth >= MAX_DEPTH {
return Err(format!(
"nested more than {MAX_DEPTH} deep at character {}",
self.at
));
}
self.depth = self.depth.saturating_add(1);
Ok(())
}
fn ascend(&mut self) {
self.depth = self.depth.saturating_sub(1);
}
fn value(&mut self) -> Result<Json, String> {
match self.peek() {
Some('{') => self.object(),
Some('[') => self.array(),
Some('"') => Ok(Json::Text(self.string()?)),
Some('t') => self.word("true", Json::Bool(true)),
Some('f') => self.word("false", Json::Bool(false)),
Some('n') => self.word("null", Json::Null),
Some(character) if character == '-' || character.is_ascii_digit() => self.number(),
Some(character) => Err(format!("unexpected '{character}' at character {}", self.at)),
None => Err("unexpected end of input".to_string()),
}
}
fn word(&mut self, word: &str, value: Json) -> Result<Json, String> {
for wanted in word.chars() {
self.expect(wanted)?;
}
Ok(value)
}
fn object(&mut self) -> Result<Json, String> {
self.descend()?;
let produced = self.object_body();
self.ascend();
produced
}
fn object_body(&mut self) -> Result<Json, String> {
self.expect('{')?;
let mut pairs = Vec::new();
self.skip_space();
if self.peek() == Some('}') {
self.at += 1;
return Ok(Json::Object(pairs));
}
loop {
self.skip_space();
let name = self.string()?;
self.skip_space();
self.expect(':')?;
self.skip_space();
let value = self.value()?;
pairs.push((name, value));
self.skip_space();
match self.next() {
Some(',') => continue,
Some('}') => return Ok(Json::Object(pairs)),
Some(character) => {
return Err(format!(
"expected ',' or '}}' at character {}, found '{character}'",
self.at - 1
))
}
None => return Err("unterminated object".to_string()),
}
}
}
fn array(&mut self) -> Result<Json, String> {
self.descend()?;
let produced = self.array_body();
self.ascend();
produced
}
fn array_body(&mut self) -> Result<Json, String> {
self.expect('[')?;
let mut items = Vec::new();
self.skip_space();
if self.peek() == Some(']') {
self.at += 1;
return Ok(Json::Array(items));
}
loop {
self.skip_space();
items.push(self.value()?);
self.skip_space();
match self.next() {
Some(',') => continue,
Some(']') => return Ok(Json::Array(items)),
Some(character) => {
return Err(format!(
"expected ',' or ']' at character {}, found '{character}'",
self.at - 1
))
}
None => return Err("unterminated array".to_string()),
}
}
}
fn string(&mut self) -> Result<String, String> {
self.expect('"')?;
let mut out = String::new();
loop {
match self.next() {
Some('"') => return Ok(out),
Some('\\') => match self.next() {
Some('"') => out.push('"'),
Some('\\') => out.push('\\'),
Some('/') => out.push('/'),
Some('n') => out.push('\n'),
Some('r') => out.push('\r'),
Some('t') => out.push('\t'),
Some('b') => out.push('\u{8}'),
Some('f') => out.push('\u{c}'),
Some('u') => out.push(self.escape_sequence()?),
Some(character) => {
return Err(format!("unknown escape '\\{character}'"));
}
None => return Err("unterminated escape".to_string()),
},
Some(character) => out.push(character),
None => return Err("unterminated string".to_string()),
}
}
}
fn escape_sequence(&mut self) -> Result<char, String> {
let first = self.hex4()?;
if (0xD800..0xDC00).contains(&first) {
self.expect('\\')?;
self.expect('u')?;
let second = self.hex4()?;
if !(0xDC00..0xE000).contains(&second) {
return Err("a high surrogate was not followed by a low one".to_string());
}
let combined = 0x10000 + ((first - 0xD800) << 10) + (second - 0xDC00);
return char::from_u32(combined).ok_or_else(|| "invalid surrogate pair".to_string());
}
char::from_u32(first).ok_or_else(|| format!("\\u{first:04x} is not a character"))
}
fn hex4(&mut self) -> Result<u32, String> {
let mut value = 0u32;
for _ in 0..4 {
match self.next().and_then(|character| character.to_digit(16)) {
Some(digit) => value = value * 16 + digit,
None => return Err("a \\u escape needs four hexadecimal digits".to_string()),
}
}
Ok(value)
}
fn number(&mut self) -> Result<Json, String> {
let start = self.at;
if self.peek() == Some('-') {
self.at += 1;
}
let mut fractional = false;
while let Some(character) = self.peek() {
match character {
'0'..='9' => self.at += 1,
'.' | 'e' | 'E' | '+' | '-' => {
fractional = true;
self.at += 1;
}
_ => break,
}
}
let literal: String = self
.characters
.get(start..self.at)
.unwrap_or_default()
.iter()
.collect();
if literal.is_empty() {
return Err(format!("expected a number at character {start}"));
}
if !fractional {
if let Ok(number) = literal.parse::<i64>() {
return Ok(Json::Int(number));
}
}
literal
.parse::<f64>()
.map(Json::Real)
.map_err(|_| format!("'{literal}' is not a number"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scalars_round_trip() {
assert_eq!(parse("null"), Ok(Json::Null));
assert_eq!(parse("true"), Ok(Json::Bool(true)));
assert_eq!(parse("-12"), Ok(Json::Int(-12)));
assert_eq!(parse("1.5"), Ok(Json::Real(1.5)));
assert_eq!(parse("\"hi\""), Ok(text("hi")));
}
#[test]
fn an_integer_is_not_a_double() {
assert_eq!(parse("9007199254740993"), Ok(Json::Int(9007199254740993)));
assert_eq!(Json::Int(7).write(), "7");
assert_eq!(Json::Real(7.0).write(), "7.0");
}
#[test]
fn a_whole_double_reads_as_an_integer() {
assert_eq!(Json::Real(5.0).integer(), Some(5));
assert_eq!(Json::Real(5.5).integer(), None);
}
#[test]
fn objects_keep_their_order() {
let value = object(vec![("b", Json::Int(1)), ("a", Json::Int(2))]);
assert_eq!(value.write(), "{\"b\":1,\"a\":2}");
}
#[test]
fn escapes_round_trip() {
let original = "a\"b\\c\nd\te\u{1}f";
let written = text(original).write();
assert_eq!(parse(&written), Ok(text(original)));
assert!(written.contains("\\u0001"));
}
#[test]
fn a_surrogate_pair_becomes_one_character() {
assert_eq!(parse("\"\\ud83d\\ude00\""), Ok(text("\u{1f600}")));
}
#[test]
fn nesting_reads_back() {
let value = parse("{\"a\": [1, {\"b\": null}], \"c\": \"d\"}").unwrap_or(Json::Null);
assert_eq!(value.get("c").and_then(Json::text), Some("d"));
let inner = value.get("a").and_then(Json::array).unwrap_or_default();
assert_eq!(inner.len(), 2);
}
#[test]
fn a_bad_document_is_refused() {
assert!(parse("{\"a\": }").is_err());
assert!(parse("[1, 2").is_err());
assert!(parse("nul").is_err());
assert!(parse("{} {}").is_err());
}
#[test]
fn a_non_finite_double_becomes_null() {
assert_eq!(Json::Real(f64::NAN).write(), "null");
assert_eq!(Json::Real(f64::INFINITY).write(), "null");
}
#[test]
fn whitespace_is_ignored() {
assert_eq!(
parse(" {\n \"a\" : [ 1 , 2 ]\n} "),
parse("{\"a\":[1,2]}")
);
}
}
#[cfg(test)]
mod fuzz_seeded {
const CASES: usize = 20_000;
const ALPHABET: &[u8] = b"{}[]\",:0123456789.-+eEtruefalsnl /\t\n\r\0\xff";
fn next(state: &mut u64) -> u64 {
*state ^= *state << 13;
*state ^= *state >> 7;
*state ^= *state << 17;
*state
}
#[test]
fn the_request_parser_never_panics_on_arbitrary_text() {
let mut state = 0x1932_0003_u64;
let mut parsed = 0usize;
for _ in 0..CASES {
let length = (next(&mut state) % 64) as usize;
let bytes: Vec<u8> = (0..length)
.map(|_| {
let at = (next(&mut state) as usize) % ALPHABET.len();
ALPHABET.get(at).copied().unwrap_or(b'?')
})
.collect();
let text = String::from_utf8_lossy(&bytes).into_owned();
parsed += usize::from(super::parse(&text).is_ok());
}
assert!(parsed <= CASES);
}
}