use std::collections::HashMap;
use std::fmt;
use std::iter::Peekable;
use std::str::Chars;
#[derive(Debug, Clone, PartialEq)]
pub enum JsonValue {
Null,
Boolean(bool),
Number(f64),
String(String),
Array(Vec<JsonValue>),
Object(HashMap<String, JsonValue>),
}
#[derive(Debug, PartialEq)]
pub enum JsonError {
UnexpectedEndOfInput,
UnexpectedToken(char),
InvalidNumber,
InvalidEscapeSequence,
InvalidUnicodeSequence,
MissingColon,
MissingComma,
InvalidValue,
}
impl fmt::Display for JsonError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
JsonError::UnexpectedEndOfInput => write!(f, "Unexpected end of input"),
JsonError::UnexpectedToken(c) => write!(f, "Unexpected token: '{}'", c),
JsonError::InvalidNumber => write!(f, "Invalid number"),
JsonError::InvalidEscapeSequence => write!(f, "Invalid escape sequence"),
JsonError::InvalidUnicodeSequence => write!(f, "Invalid Unicode sequence"),
JsonError::MissingColon => write!(f, "Missing colon in object"),
JsonError::MissingComma => write!(f, "Missing comma in array or object"),
JsonError::InvalidValue => write!(f, "Invalid JSON value"),
}
}
}
impl std::error::Error for JsonError {}
type Result<T> = std::result::Result<T, JsonError>;
#[derive(Debug, Clone)]
pub struct SerializeOptions {
pub pretty: bool,
pub indent: String,
pub date_format: Option<String>,
pub use_snake_case: bool,
}
impl Default for SerializeOptions {
fn default() -> Self {
SerializeOptions {
pretty: false,
indent: " ".to_string(),
date_format: None,
use_snake_case: false,
}
}
}
pub struct Parser<'a> {
chars: Peekable<Chars<'a>>,
}
impl<'a> Parser<'a> {
pub fn new(input: &'a str) -> Self {
Parser {
chars: input.chars().peekable(),
}
}
pub fn parse(&mut self) -> Result<JsonValue> {
self.skip_whitespace();
let value = self.parse_value()?;
self.skip_whitespace();
if self.chars.peek().is_some() {
return Err(JsonError::UnexpectedToken(self.chars.next().unwrap()));
}
Ok(value)
}
fn skip_whitespace(&mut self) {
while let Some(&c) = self.chars.peek() {
if !c.is_whitespace() {
break;
}
self.chars.next();
}
}
fn parse_value(&mut self) -> Result<JsonValue> {
match self.chars.peek() {
Some(&'"') => self.parse_string().map(JsonValue::String),
Some(&('0'..='9') | &'-') => self.parse_number().map(JsonValue::Number),
Some(&'{') => self.parse_object().map(JsonValue::Object),
Some(&'[') => self.parse_array().map(JsonValue::Array),
Some(&'t') => self.parse_true().map(|_| JsonValue::Boolean(true)),
Some(&'f') => self.parse_false().map(|_| JsonValue::Boolean(false)),
Some(&'n') => self.parse_null().map(|_| JsonValue::Null),
Some(&c) => Err(JsonError::UnexpectedToken(c)),
None => Err(JsonError::UnexpectedEndOfInput),
}
}
fn parse_string(&mut self) -> Result<String> {
if self.chars.next() != Some('"') {
return Err(JsonError::UnexpectedToken(self.chars.next().unwrap_or('\0')));
}
let mut result = String::new();
loop {
match self.chars.next() {
Some('"') => return Ok(result),
Some('\\') => {
match self.chars.next() {
Some('"') => result.push('"'),
Some('\\') => result.push('\\'),
Some('/') => result.push('/'),
Some('b') => result.push('\u{0008}'),
Some('f') => result.push('\u{000C}'),
Some('n') => result.push('\n'),
Some('r') => result.push('\r'),
Some('t') => result.push('\t'),
Some('u') => {
let mut code = 0;
for _ in 0..4 {
match self.chars.next() {
Some(c) if c.is_ascii_hexdigit() => {
code = (code << 4) | c.to_digit(16).unwrap();
}
_ => return Err(JsonError::InvalidUnicodeSequence),
}
}
match std::char::from_u32(code) {
Some(c) => result.push(c),
None => return Err(JsonError::InvalidUnicodeSequence),
}
}
_ => return Err(JsonError::InvalidEscapeSequence),
}
}
Some(c) => result.push(c),
None => return Err(JsonError::UnexpectedEndOfInput),
}
}
}
fn parse_number(&mut self) -> Result<f64> {
let mut number = String::new();
if let Some(&'-') = self.chars.peek() {
number.push(self.chars.next().unwrap());
}
if !self.parse_digits(&mut number) {
return Err(JsonError::InvalidNumber);
}
if let Some(&'.') = self.chars.peek() {
number.push(self.chars.next().unwrap());
if !self.parse_digits(&mut number) {
return Err(JsonError::InvalidNumber);
}
}
if let Some(&'e' | &'E') = self.chars.peek() {
number.push(self.chars.next().unwrap());
if let Some(&'+' | &'-') = self.chars.peek() {
number.push(self.chars.next().unwrap());
}
if !self.parse_digits(&mut number) {
return Err(JsonError::InvalidNumber);
}
}
match number.parse::<f64>() {
Ok(n) => Ok(n),
Err(_) => Err(JsonError::InvalidNumber),
}
}
fn parse_digits(&mut self, number: &mut String) -> bool {
let mut has_digits = false;
while let Some(&c) = self.chars.peek() {
if !c.is_ascii_digit() {
break;
}
number.push(self.chars.next().unwrap());
has_digits = true;
}
has_digits
}
fn parse_object(&mut self) -> Result<HashMap<String, JsonValue>> {
if self.chars.next() != Some('{') {
return Err(JsonError::UnexpectedToken(self.chars.next().unwrap_or('\0')));
}
self.skip_whitespace();
let mut object = HashMap::new();
if let Some(&'}') = self.chars.peek() {
self.chars.next();
return Ok(object);
}
loop {
self.skip_whitespace();
let key = self.parse_string()?;
self.skip_whitespace();
if self.chars.next() != Some(':') {
return Err(JsonError::MissingColon);
}
self.skip_whitespace();
let value = self.parse_value()?;
object.insert(key, value);
self.skip_whitespace();
match self.chars.next() {
Some(',') => {
}
Some('}') => {
return Ok(object);
}
_ => return Err(JsonError::MissingComma),
}
}
}
fn parse_array(&mut self) -> Result<Vec<JsonValue>> {
if self.chars.next() != Some('[') {
return Err(JsonError::UnexpectedToken(self.chars.next().unwrap_or('\0')));
}
self.skip_whitespace();
let mut array = Vec::new();
if let Some(&']') = self.chars.peek() {
self.chars.next();
return Ok(array);
}
loop {
self.skip_whitespace();
let value = self.parse_value()?;
array.push(value);
self.skip_whitespace();
match self.chars.next() {
Some(',') => {
}
Some(']') => {
return Ok(array);
}
_ => return Err(JsonError::MissingComma),
}
}
}
fn parse_true(&mut self) -> Result<()> {
self.expect_literal("true")
}
fn parse_false(&mut self) -> Result<()> {
self.expect_literal("false")
}
fn parse_null(&mut self) -> Result<()> {
self.expect_literal("null")
}
fn expect_literal(&mut self, literal: &str) -> Result<()> {
for expected in literal.chars() {
match self.chars.next() {
Some(c) if c == expected => {}
_ => return Err(JsonError::InvalidValue),
}
}
Ok(())
}
}
pub fn to_string(value: &JsonValue) -> String {
to_string_with_options(value, &SerializeOptions::default())
}
pub fn to_string_pretty(value: &JsonValue) -> String {
let mut options = SerializeOptions::default();
options.pretty = true;
to_string_with_options(value, &options)
}
pub fn to_string_with_options(value: &JsonValue, options: &SerializeOptions) -> String {
let mut result = String::new();
serialize_value(value, &mut result, options, 0);
result
}
fn serialize_value(value: &JsonValue, output: &mut String, options: &SerializeOptions, depth: usize) {
match value {
JsonValue::Null => output.push_str("null"),
JsonValue::Boolean(b) => output.push_str(if *b { "true" } else { "false" }),
JsonValue::Number(n) => {
if n.is_nan() {
output.push_str("null");
} else if n.is_infinite() {
if n.is_sign_positive() {
output.push_str("null");
} else {
output.push_str("null");
}
} else {
if n.fract() == 0.0 && n.abs() < 1e16 {
output.push_str(&format!("{}", *n as i64));
} else {
output.push_str(&n.to_string());
}
}
},
JsonValue::String(s) => serialize_string(s, output),
JsonValue::Array(arr) => serialize_array(arr, output, options, depth),
JsonValue::Object(obj) => serialize_object(obj, output, options, depth),
}
}
fn serialize_string(s: &str, output: &mut String) {
output.push('"');
for c in s.chars() {
match c {
'"' => output.push_str("\\\""),
'\\' => output.push_str("\\\\"),
'\u{0008}' => output.push_str("\\b"),
'\u{000C}' => output.push_str("\\f"),
'\n' => output.push_str("\\n"),
'\r' => output.push_str("\\r"),
'\t' => output.push_str("\\t"),
c if c.is_control() => {
output.push_str(&format!("\\u{:04x}", c as u32));
},
c => output.push(c),
}
}
output.push('"');
}
fn serialize_array(arr: &[JsonValue], output: &mut String, options: &SerializeOptions, depth: usize) {
if arr.is_empty() {
output.push_str("[]");
return;
}
output.push('[');
if options.pretty {
output.push('\n');
}
for (i, item) in arr.iter().enumerate() {
if options.pretty {
output.push_str(&options.indent.repeat(depth + 1));
}
serialize_value(item, output, options, depth + 1);
if i < arr.len() - 1 {
output.push(',');
}
if options.pretty {
output.push('\n');
}
}
if options.pretty {
output.push_str(&options.indent.repeat(depth));
}
output.push(']');
}
fn serialize_object(obj: &HashMap<String, JsonValue>, output: &mut String, options: &SerializeOptions, depth: usize) {
if obj.is_empty() {
output.push_str("{}");
return;
}
output.push('{');
if options.pretty {
output.push('\n');
}
let mut entries: Vec<(&String, &JsonValue)> = obj.iter().collect();
entries.sort_by(|a, b| a.0.cmp(b.0));
for (i, (key, value)) in entries.iter().enumerate() {
if options.pretty {
output.push_str(&options.indent.repeat(depth + 1));
}
let key_to_use = if options.use_snake_case {
camel_to_snake(key)
} else {
key.to_string()
};
serialize_string(&key_to_use, output);
output.push(':');
if options.pretty {
output.push(' ');
}
serialize_value(value, output, options, depth + 1);
if i < entries.len() - 1 {
output.push(',');
}
if options.pretty {
output.push('\n');
}
}
if options.pretty {
output.push_str(&options.indent.repeat(depth));
}
output.push('}');
}
fn camel_to_snake(s: &str) -> String {
let mut result = String::new();
let mut prev_is_lower = false;
for (i, c) in s.char_indices() {
if c.is_uppercase() {
if i > 0 && prev_is_lower {
result.push('_');
}
result.push(c.to_lowercase().next().unwrap());
} else {
result.push(c);
prev_is_lower = true;
}
}
result
}
pub trait JsonValueExt {
fn as_bool(&self) -> Option<bool>;
fn as_number(&self) -> Option<f64>;
fn as_string(&self) -> Option<&str>;
fn as_array(&self) -> Option<&Vec<JsonValue>>;
fn as_object(&self) -> Option<&HashMap<String, JsonValue>>;
fn get(&self, key: &str) -> Option<&JsonValue>;
fn get_path(&self, path: &str) -> Option<&JsonValue>;
}
impl JsonValueExt for JsonValue {
fn as_bool(&self) -> Option<bool> {
match self {
JsonValue::Boolean(b) => Some(*b),
_ => None,
}
}
fn as_number(&self) -> Option<f64> {
match self {
JsonValue::Number(n) => Some(*n),
_ => None,
}
}
fn as_string(&self) -> Option<&str> {
match self {
JsonValue::String(s) => Some(s),
_ => None,
}
}
fn as_array(&self) -> Option<&Vec<JsonValue>> {
match self {
JsonValue::Array(a) => Some(a),
_ => None,
}
}
fn as_object(&self) -> Option<&HashMap<String, JsonValue>> {
match self {
JsonValue::Object(o) => Some(o),
_ => None,
}
}
fn get(&self, key: &str) -> Option<&JsonValue> {
match self {
JsonValue::Object(o) => o.get(key),
_ => None,
}
}
fn get_path(&self, path: &str) -> Option<&JsonValue> {
let parts: Vec<&str> = path.split('.').collect();
let mut current = self;
for part in parts {
match current {
JsonValue::Object(o) => {
current = o.get(part)?;
},
_ => return None,
}
}
Some(current)
}
}
#[macro_export]
macro_rules! json {
(null) => {
$crate::JsonValue::Null
};
(true) => {
$crate::JsonValue::Boolean(true)
};
(false) => {
$crate::JsonValue::Boolean(false)
};
($num:expr) => {
$crate::JsonValue::Number($num as f64)
};
($str:expr) => {
$crate::JsonValue::String($str.to_string())
};
([$($value:tt),*]) => {
$crate::JsonValue::Array(vec![$($crate::json!($value)),*])
};
({$($key:tt => $value:tt),*}) => {
{
let mut map = std::collections::HashMap::new();
$(
map.insert($key.to_string(), $crate::json!($value));
)*
$crate::JsonValue::Object(map)
}
};
}
pub fn parse(input: &str) -> Result<JsonValue> {
Parser::new(input).parse()
}
pub fn from_bool(b: bool) -> JsonValue {
JsonValue::Boolean(b)
}
pub fn from_number<T: Into<f64>>(n: T) -> JsonValue {
JsonValue::Number(n.into())
}
pub fn from_str<T: Into<String>>(s: T) -> JsonValue {
JsonValue::String(s.into())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_null() {
assert_eq!(parse("null").unwrap(), JsonValue::Null);
}
#[test]
fn test_parse_bool() {
assert_eq!(parse("true").unwrap(), JsonValue::Boolean(true));
assert_eq!(parse("false").unwrap(), JsonValue::Boolean(false));
}
#[test]
fn test_parse_number() {
assert_eq!(parse("123").unwrap(), JsonValue::Number(123.0));
assert_eq!(parse("-123.456").unwrap(), JsonValue::Number(-123.456));
assert_eq!(parse("1e10").unwrap(), JsonValue::Number(1e10));
}
#[test]
fn test_parse_string() {
assert_eq!(
parse("\"hello world\"").unwrap(),
JsonValue::String("hello world".to_string())
);
assert_eq!(
parse("\"escape\\\"quote\"").unwrap(),
JsonValue::String("escape\"quote".to_string())
);
}
#[test]
fn test_parse_array() {
assert_eq!(
parse("[1, 2, 3]").unwrap(),
JsonValue::Array(vec![
JsonValue::Number(1.0),
JsonValue::Number(2.0),
JsonValue::Number(3.0)
])
);
}
#[test]
fn test_parse_object() {
let parsed = parse("{\"name\":\"John\",\"age\":30}").unwrap();
match parsed {
JsonValue::Object(obj) => {
assert_eq!(obj.len(), 2);
assert_eq!(obj.get("name").unwrap(), &JsonValue::String("John".to_string()));
assert_eq!(obj.get("age").unwrap(), &JsonValue::Number(30.0));
},
_ => panic!("Expected object"),
}
}
#[test]
fn test_serialize() {
let mut hobbies = Vec::new();
hobbies.push(JsonValue::String("reading".to_string()));
hobbies.push(JsonValue::String("coding".to_string()));
let mut obj = HashMap::new();
obj.insert("name".to_string(), JsonValue::String("John".to_string()));
obj.insert("age".to_string(), JsonValue::Number(30.0));
obj.insert("is_active".to_string(), JsonValue::Boolean(true));
obj.insert("hobbies".to_string(), JsonValue::Array(hobbies));
let value = JsonValue::Object(obj);
let serialized = to_string(&value);
let parsed = parse(&serialized).unwrap();
assert_eq!(value, parsed);
}
#[test]
fn test_json_macro() {
let mut hobbies = Vec::new();
hobbies.push(JsonValue::String("reading".to_string()));
hobbies.push(JsonValue::String("coding".to_string()));
let mut obj = HashMap::new();
obj.insert("name".to_string(), JsonValue::String("John".to_string()));
obj.insert("age".to_string(), JsonValue::Number(30.0));
obj.insert("is_active".to_string(), JsonValue::Boolean(true));
obj.insert("hobbies".to_string(), JsonValue::Array(hobbies));
let obj = JsonValue::Object(obj);
assert_eq!(obj.get("name").unwrap().as_string().unwrap(), "John");
assert_eq!(obj.get("age").unwrap().as_number().unwrap(), 30.0);
assert_eq!(obj.get("is_active").unwrap().as_bool().unwrap(), true);
let hobbies = obj.get("hobbies").unwrap().as_array().unwrap();
assert_eq!(hobbies.len(), 2);
assert_eq!(hobbies[0].as_string().unwrap(), "reading");
assert_eq!(hobbies[1].as_string().unwrap(), "coding");
}
}