use std::collections::HashMap;
use std::fmt;
use std::str::Chars;
use std::iter::Peekable;
#[derive(PartialEq, Debug)]
pub enum JJSON {
Object(HashMap::<String, JJSON>),
Array(Vec<JJSON>),
String(String),
Number(f64),
Boolean(bool),
}
#[macro_export]
macro_rules! jjson {
([ $( $value:expr ),+ ]) => {
{
let mut array = Vec::new();
$( array.push( jjson!($value) ); )+
JJSON::Array(array)
}
};
({ $( $key:tt : $value:expr ),+ }) => {
{
let mut object = std::collections::HashMap::new();
$( object.insert( $key.to_string(), jjson!($value) ); )+
JJSON::Object(object)
}
};
( $value:expr ) => {
JJSON::from($value)
};
}
#[macro_export]
macro_rules! hashmap {
({ $( $key:tt : $value:expr ),+ }) => {
{
let mut hashmap = std::collections::HashMap::new();
$( hashmap.insert( $key.to_string(), $value ); )+
hashmap
}
};
}
impl JJSON {
pub fn parse(s: &str) -> Result<Self, String> {
Parser::new(s).parse(None)
}
}
macro_rules! from_jjson_to_hashmap_or_vec {
($type:ty) => {
impl From<JJSON> for HashMap<String, $type> {
fn from(jjson: JJSON) -> Self {
match jjson {
JJSON::Object(object) => {
let mut map = HashMap::new();
for (key, value) in object {
map.insert(key, value.into());
}
map
},
_ => panic!("invalid JJSON::Object"),
}
}
}
impl From<JJSON> for Vec<$type> {
fn from(jjson: JJSON) -> Self {
match jjson {
JJSON::Array(array) => {
let mut list = Vec::new();
for value in array {
list.push(value.into());
}
list
},
_ => panic!("invalid JJSON::Array"),
}
}
}
};
($type:ty, $($types:ty),+) => {
from_jjson_to_hashmap_or_vec!($type);
from_jjson_to_hashmap_or_vec!($($types),+);
};
}
from_jjson_to_hashmap_or_vec!(String, i32, f64, bool);
macro_rules! from_hashmap_or_vec_to_jjson {
($type:ty) => {
impl From<HashMap<String, $type>> for JJSON {
fn from(map: HashMap<String, $type>) -> Self {
let mut object: HashMap<String, Self> = HashMap::new();
for (key, value) in map {
object.insert(key, Self::from(value));
}
Self::Object(object)
}
}
impl From<HashMap<&str, $type>> for JJSON {
fn from(map: HashMap<&str, $type>) -> Self {
let mut object: HashMap<String, Self> = HashMap::new();
for (key, value) in map {
object.insert(String::from(key), Self::from(value));
}
Self::Object(object)
}
}
impl From<Vec<$type>> for JJSON {
fn from(list: Vec<$type>) -> Self {
let mut array: Vec<Self> = Vec::new();
for value in list {
array.push(value.into());
}
Self::Array(array)
}
}
};
($type:ty, $($types:ty),+) => {
from_hashmap_or_vec_to_jjson!($type);
from_hashmap_or_vec_to_jjson!($($types),+);
};
}
from_hashmap_or_vec_to_jjson!(String, &str, i32, f64, bool);
impl From<HashMap<&str, JJSON>> for JJSON {
fn from(list: HashMap<&str, Self>) -> Self {
let mut object: HashMap<String, Self> = HashMap::new();
for (key, value) in list {
object.insert(String::from(key), value);
}
Self::Object(object)
}
}
impl From<Vec<JJSON>> for JJSON {
fn from(list: Vec<Self>) -> Self {
let mut array: Vec<Self> = Vec::new();
for value in list {
array.push(value);
}
Self::Array(array)
}
}
impl From<String> for JJSON {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<JJSON> for String {
fn from(value: JJSON) -> Self {
match value {
JJSON::String(value) => value,
_ => panic!("invalid JJSON::String"),
}
}
}
macro_rules! from_reference_to_jjson {
($type:ty) => {
impl From<&$type> for JJSON {
fn from(value: &$type) -> Self {
Self::from(*value)
}
}
};
($type:ty, $($types:ty),+) => {
from_reference_to_jjson!($type);
from_reference_to_jjson!($($types),+);
};
}
from_reference_to_jjson!(&str, i32, f64, bool);
impl From<&str> for JJSON {
fn from(value: &str) -> Self {
Self::String(String::from(value))
}
}
impl From<i32> for JJSON {
fn from(value: i32) -> Self {
Self::Number(value as f64)
}
}
impl From<JJSON> for i32 {
fn from(value: JJSON) -> Self {
match value {
JJSON::Number(value) => value as i32,
_ => panic!("invalid JJSON::Number"),
}
}
}
impl From<f64> for JJSON {
fn from(value: f64) -> Self {
Self::Number(value)
}
}
impl From<JJSON> for f64 {
fn from(value: JJSON) -> Self {
match value {
JJSON::Number(value) => value,
_ => panic!("invalid JJSON::Number"),
}
}
}
impl From<bool> for JJSON {
fn from(value: bool) -> Self {
Self::Boolean(value)
}
}
impl From<JJSON> for bool {
fn from(value: JJSON) -> Self {
match value {
JJSON::Boolean(value) => value,
_ => panic!("invalid JJSON::Boolean"),
}
}
}
impl fmt::Display for JJSON {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
Self::Object(object) => {
write!(f, "{{")?;
for (index, (key, value)) in object.iter().enumerate() {
if index > 0 {
write!(f, ",")?;
}
write!(f, "\"{}\":{}", key.replace("\"", "\\\""), &value.to_string())?;
}
write!(f, "}}")
},
Self::Array(array) => {
write!(f, "[")?;
for (index, value) in array.iter().enumerate() {
if index > 0 {
write!(f, ",")?;
}
write!(f, "{}", &value.to_string())?;
}
write!(f, "]")
},
Self::String(string) => {
let escaped_string = string
.replace("\"", "\\\"")
.replace("\\", "\\\\")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
write!(f, "\"{}\"", escaped_string)
},
Self::Number(number) => {
write!(f, "{}", number)
},
Self::Boolean(boolean) => {
write!(f, "{}", boolean)
},
}
}
}
#[derive(Debug)]
enum Token {
LeftBracket,
RightBracket,
LeftBrace,
RightBrace,
Colon,
Comma,
String(String),
Number(f64),
Boolean(bool),
Error(&'static str),
End,
}
struct Tokenizer<'a> {
chars: Peekable<Chars<'a>>,
line: usize,
column: usize,
error: bool,
}
impl<'a> Tokenizer<'a> {
fn scan_error(&mut self, message: &'static str) -> Token {
self.error = true;
self.scan_token(Token::Error(message), 0)
}
fn scan_token(&mut self, token: Token, length: usize) -> Token {
self.column += length;
token
}
fn skip_whitespace(&mut self) {
while let Some(x) = self.chars.peek() {
if !x.is_whitespace() {
break;
}
if *x == '\n' {
self.line += 1;
self.column = 0;
} else {
self.column += 1;
}
self.chars.next();
}
}
fn scan_string(&mut self) -> Token {
let mut length = 1;
let mut is_escaping = false;
let mut string = String::new();
while let Some(x) = self.chars.peek() {
let x = *x;
if x == '"' && !is_escaping {
break;
}
if is_escaping {
match x {
'"' => string.push('"'),
'\\' => string.push('\\'),
'n' => string.push('\n'),
'r' => string.push('\r'),
't' => string.push('\t'),
'u' => {
self.chars.next();
let mut hex_string = String::new();
while let Some(hex) = self.chars.peek() {
let hex = *hex;
if !hex.is_ascii_hexdigit() {
break;
}
hex_string.push(hex);
length += 1;
self.chars.next();
}
if let Ok(hex_number) = u32::from_str_radix(&hex_string, 16) {
if let Some(hex_char) = std::char::from_u32(hex_number) {
string.push(hex_char);
} else {
return self.scan_error("invalid hex char");
}
} else {
return self.scan_error("invalid hex number");
}
},
_ => {
return self.scan_error("invalid escape");
},
}
is_escaping = false;
} else if x == '\\' {
is_escaping = true;
} else {
string.push(x);
}
self.chars.next();
length += 1;
}
if self.chars.next().is_none() {
return self.scan_error("invalid string")
}
length += 1;
self.scan_token(Token::String(string), length)
}
fn scan_number(&mut self, c: char) -> Token {
let mut length = 1;
let mut string = String::new();
string.push(c);
while let Some(x) = self.chars.peek() {
let x = *x;
if !(x.is_ascii_digit() || x == '.' || x == '-') {
break;
}
string.push(x);
length += 1;
self.chars.next();
}
if let Ok(number) = string.parse::<f64>() {
return self.scan_token(Token::Number(number), length);
} else {
return self.scan_error("invalid number")
}
}
fn scan_identifier(&mut self, c: char) -> Token {
let mut length = 1;
let mut string = String::new();
string.push(c);
while let Some(x) = self.chars.peek() {
let x = *x;
if !x.is_alphabetic() {
break;
}
string.push(x);
length += 1;
self.chars.next();
}
if string == "true" {
return self.scan_token(Token::Boolean(true), length);
} else if string == "false" {
return self.scan_token(Token::Boolean(false), length);
} else {
return self.scan_error("invalid identifier")
}
}
fn scan(&mut self) -> Token {
if self.error {
return Token::End;
}
self.skip_whitespace();
match self.chars.next() {
Some('{') => self.scan_token(Token::LeftBrace, 1),
Some('}') => self.scan_token(Token::RightBrace, 1),
Some('[') => self.scan_token(Token::LeftBracket, 1),
Some(']') => self.scan_token(Token::RightBracket, 1),
Some(':') => self.scan_token(Token::Colon, 1),
Some(',') => self.scan_token(Token::Comma, 1),
Some('"') => self.scan_string(),
Some(c) if c.is_ascii_digit() || c == '-' => self.scan_number(c),
Some(c) if c.is_alphabetic() => self.scan_identifier(c),
_ => self.scan_error("invalid char"),
}
}
}
impl<'a> Tokenizer<'a> {
fn new(s: &'a str) -> Self {
Tokenizer {
chars: s.chars().peekable(),
line: 1,
column: 0,
error: false,
}
}
}
struct Parser<'a> {
tokens: Tokenizer<'a>,
}
impl<'a> Parser<'a> {
fn new(s: &'a str) -> Self {
Parser {
tokens: Tokenizer::new(s),
}
}
fn error(&self, message: &'static str) -> String {
format!("Invalid JSON: {}, line = {}, column = {}", message, self.tokens.line, self.tokens.column)
}
fn parse(&mut self, start_token: Option<Token>) -> Result<JJSON, String> {
let token = if let Some(token) = start_token { token } else { self.tokens.scan() };
match token {
Token::LeftBrace => {
let mut is_closed = false;
let mut object = HashMap::new();
let mut next_token = self.tokens.scan();
loop {
if let Token::End = next_token {
break;
}
if let Token::RightBrace = next_token {
is_closed = true;
break;
}
if let Token::String(key) = next_token {
if let Token::Colon = self.tokens.scan() {
let element = self.parse(None)?;
object.insert(key, element);
match self.tokens.scan() {
Token::Comma => {},
token => {
next_token = token;
continue;
},
}
} else {
return Err(self.error(": required"));
}
} else {
return Err(self.error("key required"));
}
next_token = self.tokens.scan();
}
if !is_closed {
return Err(self.error("} required"));
}
Ok(JJSON::Object(object))
},
Token::LeftBracket => {
let mut is_closed = false;
let mut array: Vec<JJSON> = Vec::new();
let mut next_token = self.tokens.scan();
loop {
if let Token::End = next_token {
break;
}
if let Token::RightBracket = next_token {
is_closed = true;
break;
}
let element = self.parse(Some(next_token))?;
array.push(element);
match self.tokens.scan() {
Token::Comma => {},
token => {
next_token = token;
continue;
},
}
next_token = self.tokens.scan();
}
if !is_closed {
return Err(self.error("] required"));
}
Ok(JJSON::Array(array))
},
Token::String(string) => Ok(JJSON::String(string)),
Token::Number(number) => Ok(JJSON::Number(number)),
Token::Boolean(boolean) => Ok(JJSON::Boolean(boolean)),
Token::Error(message) => Err(self.error(message)),
_ => Err(self.error("invalid token")),
}
}
}
#[cfg(test)]
mod tests {
use super::JJSON;
#[test]
fn parse_number() {
let number = JJSON::parse(" 12.12 ").unwrap();
assert_eq!(JJSON::Number(12.12), number);
let number = JJSON::parse(" -12.12 ").unwrap();
assert_eq!(JJSON::Number(-12.12), number);
}
#[test]
fn parse_string() {
let string = JJSON::parse(" \"12.12\" ").unwrap();
assert_eq!(JJSON::String(String::from("12.12")), string);
let string = JJSON::parse(" \"12\n.12\" ").unwrap();
assert_eq!(JJSON::String(String::from("12\n.12")), string);
}
}