use std::array;
use std::collections::HashMap;
use std::iter::Peekable;
use std::ops::Index;
use std::str::CharIndices;
#[derive(Debug, PartialEq)]
pub(crate) enum Json<'a> {
Null,
Bool(bool),
Integer(i64),
Number(f64),
String(&'a str),
Array(Vec<Json<'a>>),
Object(HashMap<&'a str, Json<'a>>),
}
impl<'a> Index<&str> for Json<'a> {
type Output = Json<'a>;
fn index(&self, key: &str) -> &Self::Output {
match self {
Json::Object(map) => map.get(key).unwrap_or(&Json::Null),
_ => &Json::Null,
}
}
}
impl PartialEq<&str> for Json<'_> {
fn eq(&self, other: &&str) -> bool {
matches!(self, Json::String(s) if s == other)
}
}
impl<'a> Json<'a> {
pub(crate) fn as_str(&self) -> Option<&'a str> {
match self {
Json::String(s) => Some(s),
_ => None,
}
}
pub(crate) fn as_i64(&self) -> Option<i64> {
match self {
Json::Integer(i) => Some(*i),
Json::Number(n) if n.fract() == 0.0 => Some(*n as i64),
_ => None,
}
}
pub(crate) fn as_u64(&self) -> Option<u64> {
self.as_i64()
.and_then(|i| if i >= 0 { Some(i as u64) } else { None })
}
pub(crate) fn as_array(&self) -> Option<&[Json<'a>]> {
match self {
Json::Array(arr) => Some(arr),
_ => None,
}
}
pub(crate) fn as_object(&self) -> Option<&HashMap<&'a str, Json<'a>>> {
match self {
Json::Object(map) => Some(map),
_ => None,
}
}
}
pub(crate) fn parse<'a>(s: &'a str) -> Result<Json<'a>, &'static str> {
let s = s.trim_ascii();
let mut chars = s.char_indices().peekable();
let value = parse_value(s, &mut chars)?;
Ok(value)
}
fn parse_value<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<Json<'a>, &'static str> {
skip_whitespace(chars);
match chars.peek() {
Some((_, '{')) => parse_object(s, chars),
Some((_, '[')) => parse_array(s, chars),
Some((_, '"')) => parse_string(s, chars).map(Json::String),
Some((_, 't' | 'f')) => parse_bool(chars),
Some((_, 'n')) => parse_null(chars),
Some((_, '-' | '0'..='9')) => parse_number(chars),
Some(_) => Err("unexpected character"),
None => Err("unexpected end of input"),
}
}
fn parse_object<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<Json<'a>, &'static str> {
chars.next();
let mut map = HashMap::new();
skip_whitespace(chars);
if matches!(chars.peek(), Some((_, '}'))) {
chars.next();
return Ok(Json::Object(map));
}
loop {
skip_whitespace(chars);
let key = parse_string(s, chars)?;
skip_whitespace(chars);
if !matches!(chars.next(), Some((_, ':'))) {
return Err("expected ':'");
}
let value = parse_value(s, chars)?;
map.insert(key, value);
skip_whitespace(chars);
match chars.next() {
Some((_, ',')) => continue,
Some((_, '}')) => break,
_ => return Err("expected ',' or '}'"),
}
}
Ok(Json::Object(map))
}
fn parse_array<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<Json<'a>, &'static str> {
chars.next();
let mut arr = Vec::new();
skip_whitespace(chars);
if matches!(chars.peek(), Some((_, ']'))) {
chars.next();
return Ok(Json::Array(arr));
}
loop {
skip_whitespace(chars);
arr.push(parse_value(s, chars)?);
skip_whitespace(chars);
match chars.next() {
Some((_, ',')) => continue,
Some((_, ']')) => return Ok(Json::Array(arr)),
_ => return Err("expected ',' or ']'"),
}
}
}
fn parse_string<'a>(s: &'a str, chars: &mut Peekable<CharIndices>) -> Result<&'a str, &'static str> {
if !matches!(chars.next(), Some((_, '"'))) {
return Err("expected string starting with '\"'");
}
let start = chars.peek().map(|(i, _)| *i).unwrap_or(0);
for (i, c) in chars {
if c == '"' {
return Ok(&s[start..i]);
}
}
Err("unterminated string")
}
fn parse_number(chars: &mut Peekable<CharIndices>) -> Result<Json<'static>, &'static str> {
let mut is_float = false;
let mut num = String::new();
while let Some((_, c @ ('0'..='9' | '-' | '.' | 'e' | 'E' | '+'))) = chars.peek() {
num.push(*c);
is_float = is_float || matches!(c, '.' | 'e' | 'E');
chars.next();
}
if !is_float {
let i = num.parse::<i64>().map_err(|_| "invalid integer")?;
return Ok(Json::Integer(i));
}
let n = num.parse::<f64>().map_err(|_| "invalid number")?;
Ok(Json::Number(n))
}
fn parse_bool(chars: &mut Peekable<CharIndices>) -> Result<Json<'static>, &'static str> {
let bool = next_chars(chars);
if bool == [Some('t'), Some('r'), Some('u'), Some('e')] {
return Ok(Json::Bool(true));
}
if bool == [Some('f'), Some('a'), Some('l'), Some('s')] && matches!(chars.next(), Some((_, 'e'))) {
return Ok(Json::Bool(false));
}
Err("invalid boolean literal")
}
fn parse_null(chars: &mut Peekable<CharIndices>) -> Result<Json<'static>, &'static str> {
if next_chars(chars) == [Some('n'), Some('u'), Some('l'), Some('l')] {
return Ok(Json::Null);
}
Err("invalid \"null\" literal")
}
fn skip_whitespace(chars: &mut Peekable<CharIndices>) {
while let Some((_, ' ' | '\n' | '\r' | '\t')) = chars.peek() {
chars.next();
}
}
fn next_chars<const N: usize>(chars: &mut Peekable<CharIndices>) -> [Option<char>; N] {
array::from_fn(|_| chars.next().map(|(_, c)| c))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse() {
assert_eq!(parse("null").unwrap(), Json::Null);
assert_eq!(parse("true").unwrap(), Json::Bool(true));
assert_eq!(parse("false").unwrap(), Json::Bool(false));
assert_eq!(parse("42").unwrap(), Json::Integer(42));
assert_eq!(parse("42.0").unwrap(), Json::Number(42.0));
assert_eq!(parse(r#""hello""#).unwrap(), Json::String("hello"));
assert_eq!(
parse("[1,2.0,3]").unwrap(),
Json::Array(vec![Json::Integer(1), Json::Number(2.0), Json::Integer(3)])
);
let mut obj = HashMap::new();
obj.insert("key", Json::String("value"));
assert_eq!(parse(r#"{"key":"value"}"#).unwrap(), Json::Object(obj));
}
#[test]
fn test_whitespace_handling() {
assert_eq!(parse(" null ").unwrap(), Json::Null);
assert_eq!(parse(" true ").unwrap(), Json::Bool(true));
assert_eq!(
parse(" [ 1 , 2.0 , 3 ] ").unwrap(),
Json::Array(vec![Json::Integer(1), Json::Number(2.0), Json::Integer(3)])
);
let mut obj = HashMap::new();
obj.insert("key", Json::String("value"));
assert_eq!(parse(r#" { "key" : "value" } "#).unwrap(), Json::Object(obj));
}
#[test]
fn test_empty_collections() {
assert_eq!(parse("[]").unwrap(), Json::Array(vec![]));
assert_eq!(parse("{}").unwrap(), Json::Object(HashMap::new()));
assert_eq!(parse("[ ]").unwrap(), Json::Array(vec![]));
assert_eq!(parse("{ }").unwrap(), Json::Object(HashMap::new()));
}
#[test]
fn test_nested_structures() {
assert_eq!(
parse(r#"{"nested":{"inner":"value"}}"#).unwrap(),
Json::Object({
let mut outer = HashMap::new();
let mut inner = HashMap::new();
inner.insert("inner", Json::String("value"));
outer.insert("nested", Json::Object(inner));
outer
})
);
assert_eq!(
parse("[[1,2],[3,4]]").unwrap(),
Json::Array(vec![
Json::Array(vec![Json::Integer(1), Json::Integer(2)]),
Json::Array(vec![Json::Integer(3), Json::Integer(4)])
])
);
}
#[test]
fn test_numbers() {
assert_eq!(parse("0").unwrap(), Json::Integer(0));
assert_eq!(parse("-42").unwrap(), Json::Integer(-42));
assert_eq!(parse("3.14").unwrap(), Json::Number(3.14));
assert_eq!(parse("-3.14").unwrap(), Json::Number(-3.14));
assert_eq!(parse("1e10").unwrap(), Json::Number(1e10));
assert_eq!(parse("1E10").unwrap(), Json::Number(1E10));
assert_eq!(parse("1e-10").unwrap(), Json::Number(1e-10));
assert_eq!(parse("1.5e+10").unwrap(), Json::Number(1.5e+10));
}
#[test]
fn test_strings() {
assert_eq!(parse(r#""""#).unwrap(), Json::String(""));
assert_eq!(parse(r#""hello world""#).unwrap(), Json::String("hello world"));
assert_eq!(
parse(r#""with spaces and 123""#).unwrap(),
Json::String("with spaces and 123")
);
}
#[test]
fn test_mixed_array() {
assert_eq!(
parse(r#"[null, true, false, 35.1, 42, "text", [], {}]"#).unwrap(),
Json::Array(vec![
Json::Null,
Json::Bool(true),
Json::Bool(false),
Json::Number(35.1),
Json::Integer(42),
Json::String("text"),
Json::Array(vec![]),
Json::Object(HashMap::new())
])
);
}
#[test]
fn test_object_multiple_keys() {
let mut obj = HashMap::new();
obj.insert("a", Json::Integer(1));
obj.insert("b", Json::Bool(true));
obj.insert("c", Json::Null);
assert_eq!(parse(r#"{"a":1,"b":true,"c":null}"#).unwrap(), Json::Object(obj));
}
#[test]
fn test_error_cases() {
assert!(parse("").is_err());
assert!(parse("nul").is_err());
assert!(parse("tru").is_err());
assert!(parse("fals").is_err());
assert!(parse(r#""unterminated"#).is_err());
assert!(parse("[1,2,]").is_err());
assert!(parse(r#"{"key""#).is_err());
assert!(parse(r#"{"key":"value""#).is_err());
assert!(parse(r#"{"key":"value",}"#).is_err());
assert!(parse("invalid").is_err());
assert!(parse("[1 2]").is_err());
assert!(parse(r#"{"key":"value" "key2":"value2"}"#).is_err());
}
}