use std::collections::HashMap;
use std::fmt;
#[derive( Debug, Clone, PartialEq )]
pub enum JsonValue
{
Null,
Bool( bool ),
Number( f64 ),
String( String ),
Array( Vec< JsonValue > ),
Object( HashMap< String, JsonValue > ),
}
#[derive( Debug, Clone )]
pub struct JsonError
{
pub message : String,
pub position : usize,
}
impl fmt::Display for JsonError
{
fn fmt( &self, f : &mut fmt::Formatter< '_ > ) -> fmt::Result
{
write!( f, "JSON parse error at position {}: {}", self.position, self.message )
}
}
impl core::error::Error for JsonError {}
pub type Result< T > = core::result::Result< T, JsonError >;
struct Parser< 'a >
{
input : &'a str,
position : usize,
}
impl< 'a > Parser< 'a >
{
fn new( input : &'a str ) -> Self
{
Self { input, position : 0 }
}
fn parse( &mut self ) -> Result< JsonValue >
{
self.skip_whitespace();
let value = self.parse_value()?;
self.skip_whitespace();
if self.position < self.input.len()
{
return Err( self.error( "unexpected content after JSON value" ) );
}
Ok( value )
}
fn parse_value( &mut self ) -> Result< JsonValue >
{
self.skip_whitespace();
if self.position >= self.input.len()
{
return Err( self.error( "unexpected end of input" ) );
}
let ch = self.peek()?;
match ch
{
'n' => self.parse_null(),
't' | 'f' => self.parse_bool(),
'"' => Ok( JsonValue::String( self.parse_string()? ) ),
'[' => self.parse_array(),
'{' => self.parse_object(),
'-' | '0'..='9' => self.parse_number(),
_ => Err( self.error( &format!( "unexpected character '{ch}'" ) ) ),
}
}
fn parse_null( &mut self ) -> Result< JsonValue >
{
if self.consume_str( "null" )
{
Ok( JsonValue::Null )
}
else
{
Err( self.error( "expected 'null'" ) )
}
}
fn parse_bool( &mut self ) -> Result< JsonValue >
{
if self.consume_str( "true" )
{
Ok( JsonValue::Bool( true ) )
}
else if self.consume_str( "false" )
{
Ok( JsonValue::Bool( false ) )
}
else
{
Err( self.error( "expected 'true' or 'false'" ) )
}
}
fn parse_number( &mut self ) -> Result< JsonValue >
{
let start = self.position;
if self.peek()? == '-'
{
self.advance();
}
if self.peek()? == '0'
{
self.advance();
}
else if self.peek()?.is_ascii_digit()
{
while self.position < self.input.len() && self.peek()?.is_ascii_digit()
{
self.advance();
}
}
else
{
return Err( self.error( "invalid number" ) );
}
if self.position < self.input.len() && self.peek()? == '.'
{
self.advance();
if !self.peek()?.is_ascii_digit()
{
return Err( self.error( "expected digit after decimal point" ) );
}
while self.position < self.input.len() && self.peek()?.is_ascii_digit()
{
self.advance();
}
}
if self.position < self.input.len() && ( self.peek()? == 'e' || self.peek()? == 'E' )
{
self.advance();
if self.position < self.input.len() && ( self.peek()? == '+' || self.peek()? == '-' )
{
self.advance();
}
if !self.peek()?.is_ascii_digit()
{
return Err( self.error( "expected digit in exponent" ) );
}
while self.position < self.input.len() && self.peek()?.is_ascii_digit()
{
self.advance();
}
}
let number_str = &self.input[ start..self.position ];
number_str.parse::< f64 >()
.map( JsonValue::Number )
.map_err( | _ | self.error( "invalid number format" ) )
}
fn parse_string( &mut self ) -> Result< String >
{
if self.peek()? != '"'
{
return Err( self.error( "expected '\"'" ) );
}
self.advance();
let mut result = String::new();
loop
{
if self.position >= self.input.len()
{
return Err( self.error( "unterminated string" ) );
}
let ch = self.current()?;
if ch == '"'
{
self.advance(); break;
}
else if ch == '\\'
{
self.advance();
if self.position >= self.input.len()
{
return Err( self.error( "unterminated escape sequence" ) );
}
let escaped = self.current()?;
match escaped
{
'"' => result.push( '"' ),
'\\' => result.push( '\\' ),
'/' => result.push( '/' ),
'b' => result.push( '\x08' ),
'f' => result.push( '\x0C' ),
'n' => result.push( '\n' ),
'r' => result.push( '\r' ),
't' => result.push( '\t' ),
'u' =>
{
self.advance();
let hex = self.parse_unicode_escape()?;
result.push( hex );
continue;
}
_ => return Err( self.error( &format!( "invalid escape sequence '\\{escaped}'" ) ) ),
}
self.advance();
}
else
{
result.push( ch );
self.advance();
}
}
Ok( result )
}
fn parse_unicode_escape( &mut self ) -> Result< char >
{
if self.position + 4 > self.input.len()
{
return Err( self.error( "incomplete unicode escape" ) );
}
let hex_str = &self.input[ self.position..self.position + 4 ];
let code = u32::from_str_radix( hex_str, 16 )
.map_err( | _ | self.error( "invalid unicode escape" ) )?;
self.position += 4;
if ( 0xD800..=0xDBFF ).contains( &code )
{
let has_next_escape = self.position + 2 <= self.input.len()
&& &self.input[ self.position..self.position + 2 ] == "\\u";
if has_next_escape
{
self.position += 2;
if self.position + 4 <= self.input.len()
{
let low_hex_str = &self.input[ self.position..self.position + 4 ];
if let Ok( low_code ) = u32::from_str_radix( low_hex_str, 16 )
{
if ( 0xDC00..=0xDFFF ).contains( &low_code )
{
self.position += 4;
let codepoint = 0x10000 + ( ( code & 0x3FF ) << 10 ) + ( low_code & 0x3FF );
return char::from_u32( codepoint )
.ok_or_else( || self.error( "invalid code point from surrogate pair" ) );
}
self.position -= 2;
}
else
{
self.position -= 2;
}
}
else
{
self.position -= 2;
}
}
Ok( char::REPLACEMENT_CHARACTER )
}
else if ( 0xDC00..=0xDFFF ).contains( &code )
{
Ok( char::REPLACEMENT_CHARACTER )
}
else
{
char::from_u32( code )
.ok_or_else( || self.error( "invalid unicode code point" ) )
}
}
fn parse_array( &mut self ) -> Result< JsonValue >
{
if self.peek()? != '['
{
return Err( self.error( "expected '['" ) );
}
self.advance(); self.skip_whitespace();
let mut elements = Vec::new();
if self.peek()? == ']'
{
self.advance();
return Ok( JsonValue::Array( elements ) );
}
loop
{
elements.push( self.parse_value()? );
self.skip_whitespace();
let ch = self.peek()?;
if ch == ']'
{
self.advance();
break;
}
else if ch == ','
{
self.advance();
self.skip_whitespace();
if self.peek()? == ']'
{
return Err( self.error( "trailing comma in array" ) );
}
}
else
{
return Err( self.error( "expected ',' or ']' in array" ) );
}
}
Ok( JsonValue::Array( elements ) )
}
fn parse_object( &mut self ) -> Result< JsonValue >
{
if self.peek()? != '{'
{
return Err( self.error( "expected '{'" ) );
}
self.advance(); self.skip_whitespace();
let mut object = HashMap::new();
if self.peek()? == '}'
{
self.advance();
return Ok( JsonValue::Object( object ) );
}
loop
{
self.skip_whitespace();
if self.peek()? != '"'
{
return Err( self.error( "expected string key in object" ) );
}
let key = self.parse_string()?;
self.skip_whitespace();
if self.peek()? != ':'
{
return Err( self.error( "expected ':' after object key" ) );
}
self.advance(); self.skip_whitespace();
let value = self.parse_value()?;
object.insert( key, value );
self.skip_whitespace();
let ch = self.peek()?;
if ch == '}'
{
self.advance();
break;
}
else if ch == ','
{
self.advance();
self.skip_whitespace();
if self.peek()? == '}'
{
return Err( self.error( "trailing comma in object" ) );
}
}
else
{
return Err( self.error( "expected ',' or '}' in object" ) );
}
}
Ok( JsonValue::Object( object ) )
}
fn skip_whitespace( &mut self )
{
while self.position < self.input.len()
{
match self.input[ self.position.. ].chars().next()
{
Some(' ' | '\t' | '\n' | '\r') =>
{
self.position += 1; },
_ => break,
}
}
}
fn peek( &self ) -> Result< char >
{
self.input[ self.position.. ].chars().next()
.ok_or_else( || self.error( "unexpected end of input" ) )
}
fn current( &self ) -> Result< char >
{
self.peek()
}
fn advance( &mut self )
{
if self.position < self.input.len()
{
if let Some( ch ) = self.input[ self.position.. ].chars().next()
{
self.position += ch.len_utf8();
}
}
}
fn consume_str( &mut self, s : &str ) -> bool
{
if self.input[ self.position.. ].starts_with( s )
{
self.position += s.len();
true
}
else
{
false
}
}
fn error( &self, message : &str ) -> JsonError
{
JsonError
{
message : message.to_string(),
position : self.position,
}
}
}
#[inline]
pub fn parse_json( input : &str ) -> Result< JsonValue >
{
let mut parser = Parser::new( input );
parser.parse()
}
impl JsonValue
{
#[must_use]
#[inline]
pub fn as_object( &self ) -> Option< &HashMap< String, JsonValue > >
{
match self
{
JsonValue::Object( obj ) => Some( obj ),
_ => None,
}
}
#[must_use]
#[inline]
pub fn as_array( &self ) -> Option< &Vec< JsonValue > >
{
match self
{
JsonValue::Array( arr ) => Some( arr ),
_ => None,
}
}
#[must_use]
#[inline]
pub fn as_str( &self ) -> Option< &str >
{
match self
{
JsonValue::String( s ) => Some( s ),
_ => None,
}
}
#[must_use]
#[inline]
pub fn as_number( &self ) -> Option< f64 >
{
match self
{
JsonValue::Number( n ) => Some( *n ),
_ => None,
}
}
#[must_use]
#[inline]
pub fn as_bool( &self ) -> Option< bool >
{
match self
{
JsonValue::Bool( b ) => Some( *b ),
_ => None,
}
}
#[must_use]
#[inline]
pub fn is_null( &self ) -> bool
{
matches!( self, JsonValue::Null )
}
#[must_use]
#[inline]
pub fn get( &self, key : &str ) -> Option< &JsonValue >
{
self.as_object()?.get( key )
}
#[must_use]
#[inline]
pub fn get_str( &self, key : &str ) -> Option< &str >
{
self.get( key )?.as_str()
}
#[must_use]
#[inline]
pub fn get_number( &self, key : &str ) -> Option< f64 >
{
self.get( key )?.as_number()
}
#[must_use]
#[inline]
pub fn get_bool( &self, key : &str ) -> Option< bool >
{
self.get( key )?.as_bool()
}
#[must_use]
#[inline]
pub fn get_object( &self, key : &str ) -> Option< &HashMap< String, JsonValue > >
{
self.get( key )?.as_object()
}
#[must_use]
#[inline]
pub fn get_array( &self, key : &str ) -> Option< &Vec< JsonValue > >
{
self.get( key )?.as_array()
}
}
#[cfg( test )]
mod tests
{
use super::*;
#[test]
fn test_parse_null()
{
let result = parse_json( "null" ).unwrap();
assert_eq!( result, JsonValue::Null );
}
#[test]
fn test_parse_bool_true()
{
let result = parse_json( "true" ).unwrap();
assert_eq!( result, JsonValue::Bool( true ) );
}
#[test]
fn test_parse_bool_false()
{
let result = parse_json( "false" ).unwrap();
assert_eq!( result, JsonValue::Bool( false ) );
}
#[test]
fn test_parse_number_integer()
{
let result = parse_json( "42" ).unwrap();
assert_eq!( result, JsonValue::Number( 42.0 ) );
}
#[test]
fn test_parse_number_negative()
{
let result = parse_json( "-123" ).unwrap();
assert_eq!( result, JsonValue::Number( -123.0 ) );
}
#[test]
fn test_parse_number_float()
{
let result = parse_json( "12.345" ).unwrap();
assert_eq!( result, JsonValue::Number( 12.345 ) );
}
#[test]
fn test_parse_number_exponent()
{
let result = parse_json( "1.5e10" ).unwrap();
assert_eq!( result, JsonValue::Number( 1.5e10 ) );
}
#[test]
fn test_parse_string_simple()
{
let result = parse_json( r#""hello""# ).unwrap();
assert_eq!( result, JsonValue::String( "hello".to_string() ) );
}
#[test]
fn test_parse_string_empty()
{
let result = parse_json( r#""""# ).unwrap();
assert_eq!( result, JsonValue::String( String::new() ) );
}
#[test]
fn test_parse_string_escape_sequences()
{
let result = parse_json( r#""hello\nworld\ttab\"quote\\backslash""# ).unwrap();
assert_eq!( result, JsonValue::String( "hello\nworld\ttab\"quote\\backslash".to_string() ) );
}
#[test]
fn test_parse_string_unicode()
{
let result = parse_json( r#""\u0041\u0042\u0043""# ).unwrap();
assert_eq!( result, JsonValue::String( "ABC".to_string() ) );
}
#[test]
fn test_parse_array_empty()
{
let result = parse_json( "[]" ).unwrap();
assert_eq!( result, JsonValue::Array( vec![] ) );
}
#[test]
fn test_parse_array_single()
{
let result = parse_json( "[42]" ).unwrap();
assert_eq!( result, JsonValue::Array( vec![ JsonValue::Number( 42.0 ) ] ) );
}
#[test]
fn test_parse_array_multiple()
{
let result = parse_json( r#"[1, "two", true, null]"# ).unwrap();
assert_eq!
(
result,
JsonValue::Array( vec!
[
JsonValue::Number( 1.0 ),
JsonValue::String( "two".to_string() ),
JsonValue::Bool( true ),
JsonValue::Null,
])
);
}
#[test]
fn test_parse_array_nested()
{
let result = parse_json( "[[1, 2], [3, 4]]" ).unwrap();
assert_eq!
(
result,
JsonValue::Array( vec!
[
JsonValue::Array( vec![ JsonValue::Number( 1.0 ), JsonValue::Number( 2.0 ) ] ),
JsonValue::Array( vec![ JsonValue::Number( 3.0 ), JsonValue::Number( 4.0 ) ] ),
])
);
}
#[test]
fn test_parse_object_empty()
{
let result = parse_json( "{}" ).unwrap();
assert_eq!( result, JsonValue::Object( HashMap::new() ) );
}
#[test]
fn test_parse_object_single()
{
let result = parse_json( r#"{"key": "value"}"# ).unwrap();
let mut expected = HashMap::new();
expected.insert( "key".to_string(), JsonValue::String( "value".to_string() ) );
assert_eq!( result, JsonValue::Object( expected ) );
}
#[test]
fn test_parse_object_multiple()
{
let result = parse_json( r#"{"a": 1, "b": true, "c": null}"# ).unwrap();
let obj = result.as_object().unwrap();
assert_eq!( obj.get( "a" ), Some( &JsonValue::Number( 1.0 ) ) );
assert_eq!( obj.get( "b" ), Some( &JsonValue::Bool( true ) ) );
assert_eq!( obj.get( "c" ), Some( &JsonValue::Null ) );
}
#[test]
fn test_parse_object_nested()
{
let result = parse_json( r#"{"outer": {"inner": 42}}"# ).unwrap();
let outer = result.as_object().unwrap();
let inner = outer.get( "outer" ).unwrap().as_object().unwrap();
assert_eq!( inner.get( "inner" ), Some( &JsonValue::Number( 42.0 ) ) );
}
#[test]
fn test_reject_trailing_comma_array()
{
let result = parse_json( "[1, 2,]" );
assert!( result.is_err() );
}
#[test]
fn test_reject_trailing_comma_object()
{
let result = parse_json( r#"{"a": 1,}"# );
assert!( result.is_err() );
}
#[test]
fn test_whitespace_handling()
{
let result = parse_json( " \n\t 42 \n\t " ).unwrap();
assert_eq!( result, JsonValue::Number( 42.0 ) );
}
#[test]
fn test_helper_methods()
{
let json = parse_json( r#"{"name": "John", "age": 30, "active": true}"# ).unwrap();
assert_eq!( json.get_str( "name" ), Some( "John" ) );
assert_eq!( json.get_number( "age" ), Some( 30.0 ) );
assert_eq!( json.get_bool( "active" ), Some( true ) );
}
#[test]
fn test_malformed_json()
{
assert!( parse_json( "{" ).is_err() );
assert!( parse_json( "[" ).is_err() );
assert!( parse_json( r#"{"key"}"# ).is_err() );
assert!( parse_json( r#"{"key": }"# ).is_err() );
}
}