use serde::{
de::{self, Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
use serde_json::Value;
use serde_json::{Map, Number};
use std::fmt::Write as _;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JhonError {
Syntax {
line: usize,
col: usize,
msg: String,
},
Eof {
line: usize,
col: usize,
msg: String,
},
DuplicateKey {
line: usize,
col: usize,
key: String,
},
Serde(String),
}
impl std::fmt::Display for JhonError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
JhonError::Syntax { line, col, msg } => {
write!(f, "parse error at {}:{}: {}", line, col, msg)
}
JhonError::Eof { line, col, msg } => {
write!(f, "unexpected end of input at {}:{}: {}", line, col, msg)
}
JhonError::DuplicateKey { line, col, key } => {
write!(f, "duplicate key at {}:{}: {:?}", line, col, key)
}
JhonError::Serde(msg) => write!(f, "deserialization error: {}", msg),
}
}
}
impl std::error::Error for JhonError {}
impl From<serde_json::Error> for JhonError {
fn from(e: serde_json::Error) -> Self {
JhonError::Serde(e.to_string())
}
}
pub type Result<T> = std::result::Result<T, JhonError>;
macro_rules! syntax_err {
($($arg:tt)*) => {
$crate::JhonError::Syntax { line: 0, col: 0, msg: format!($($arg)*) }
};
}
#[inline]
pub fn parse(text: &str) -> Result<Value> {
{
let mut probe = Parser::new(text.as_bytes());
probe.skip_ws_and_comments();
if probe.current().is_none() {
return Ok(Value::Null);
}
}
let input = text.trim();
let mut detector = Parser::new(input.as_bytes());
detector.skip_ws_and_comments();
let first_byte = detector.current();
let object_mode = match first_byte {
None | Some(b'{') | Some(b'[') => false,
Some(_) => {
let mut probe = detector;
match probe.parse_key() {
Ok(_) => {
probe.skip_ws_and_comments();
probe.current() == Some(b'=')
}
Err(_) => false,
}
}
};
if object_mode {
parse_jhon_object(input)
} else {
parse_jhon_array(input)
}
}
#[inline]
pub fn serialize(value: &Value) -> String {
let mut result = String::new();
serialize_top_compact(value, &mut result);
result
}
#[inline(always)]
fn serialize_top_compact(value: &Value, result: &mut String) {
match value {
Value::Array(arr) if arr.is_empty() => {}
Value::Array(arr) => serialize_array_contents_compact(arr, result),
Value::Object(map) if map.is_empty() => {}
Value::Null => {}
_ => serialize_compact(value, result),
}
}
pub fn serialize_pretty(value: &Value, indent: &str) -> String {
serialize_pretty_with_options(
value,
&PrettyOptions {
indent: indent.to_string(),
max_inline_width: 0,
},
)
}
#[derive(Debug, Clone)]
pub struct PrettyOptions {
pub indent: String,
pub max_inline_width: usize,
}
impl Default for PrettyOptions {
fn default() -> Self {
Self {
indent: " ".to_string(),
max_inline_width: 0,
}
}
}
pub fn serialize_pretty_with_options(value: &Value, opts: &PrettyOptions) -> String {
let mut result = String::new();
serialize_pretty_inline_top(value, &opts.indent, opts.max_inline_width, &mut result);
result
}
#[inline(always)]
fn serialize_top_pretty(value: &Value, indent: &str, result: &mut String) {
match value {
Value::Array(arr) if arr.is_empty() => {}
Value::Array(arr) => serialize_top_array_pretty(arr, indent, result),
Value::Object(map) if map.is_empty() => {}
Value::Null => {}
_ => serialize_pretty_with_depth(value, indent, 0, false, result),
}
}
fn serialize_top_array_pretty(arr: &[Value], indent: &str, result: &mut String) {
let mut first = true;
for value in arr {
if !first {
result.push('\n');
}
first = false;
match value {
Value::Object(map) if map.is_empty() => result.push_str("{}"),
Value::Object(map) => {
result.push_str("{\n");
let mut first_pair = true;
for (k, v) in map {
if !first_pair {
result.push('\n');
}
first_pair = false;
result.push_str(indent);
serialize_key(k, result);
result.push_str(" = ");
serialize_pretty_with_depth(v, indent, 1, false, result);
}
result.push('\n');
result.push('}');
}
_ => serialize_pretty_with_depth(value, indent, 0, false, result),
}
}
}
pub struct Jhon;
impl Jhon {
pub fn to_string<T: Serialize>(value: &T) -> Result<String> {
let value = serde_json::to_value(value)?;
Ok(serialize(&value))
}
pub fn to_string_pretty<T: Serialize>(value: &T, indent: &str) -> Result<String> {
let value = serde_json::to_value(value)?;
Ok(serialize_pretty(&value, indent))
}
#[allow(clippy::should_implement_trait)]
pub fn from_str<'de, T: Deserialize<'de>>(s: &'de str) -> Result<T> {
let value = parse(s)?;
T::deserialize(value).map_err(|e| JhonError::Serde(e.to_string()))
}
pub fn from_str_with_deserializer<'de, T: Deserialize<'de>, D: Deserializer<'de>>(
deserializer: D,
) -> Result<T> {
T::deserialize(deserializer).map_err(|e| JhonError::Serde(e.to_string()))
}
}
pub mod jhon {
use super::*;
pub fn serialize<T: Serialize, S: Serializer>(
value: &T, serializer: S,
) -> std::result::Result<S::Ok, S::Error> {
let jhon_string = Jhon::to_string(value).map_err(serde::ser::Error::custom)?;
jhon_string.serialize(serializer)
}
pub fn deserialize<'de, T: Deserialize<'de>, D: Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<T, D::Error> {
let json_value = serde_json::Value::deserialize(deserializer)?;
T::deserialize(json_value).map_err(de::Error::custom)
}
}
pub fn from_str<'de, T: Deserialize<'de>>(s: &'de str) -> Result<T> {
Jhon::from_str(s)
}
pub fn to_string<T: Serialize>(value: &T) -> Result<String> {
Jhon::to_string(value)
}
pub fn to_string_pretty<T: Serialize>(value: &T, indent: &str) -> Result<String> {
Jhon::to_string_pretty(value, indent)
}
const BB: u8 = b'b'; const TT: u8 = b't'; const NN: u8 = b'n'; const FF: u8 = b'f'; const RR: u8 = b'r'; const QU: u8 = b'"'; const BS: u8 = b'\\'; const UU: u8 = b'u'; const __: u8 = 0;
static ESCAPE: [u8; 256] = [
UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, ];
#[derive(Clone, Copy)]
struct Parser<'a> {
input: &'a [u8],
pos: usize,
line: usize, col: usize, }
impl<'a> Parser<'a> {
fn new(input: &'a [u8]) -> Self {
Self {
input,
pos: 0,
line: 1,
col: 1,
}
}
fn current(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
fn advance(&mut self) -> Option<u8> {
let c = self.current()?;
if c == b'\n' {
self.line += 1;
self.col = 1;
} else {
self.col += 1;
}
self.pos += 1;
Some(c)
}
fn skip_ws_and_comments(&mut self) -> bool {
let mut saw_newline = false;
loop {
match self.current() {
Some(b' ') | Some(b'\t') | Some(b'\r') => {
self.advance();
}
Some(b'\n') => {
saw_newline = true;
self.advance();
}
Some(b'/') if self.input.get(self.pos + 1) == Some(&b'/') => {
self.advance();
self.advance();
while let Some(c) = self.current() {
if c == b'\n' {
break;
}
self.advance();
}
}
Some(b'/') if self.input.get(self.pos + 1) == Some(&b'*') => {
self.advance();
self.advance();
loop {
match self.current() {
None => return saw_newline,
Some(b'*') if self.input.get(self.pos + 1) == Some(&b'/') => {
self.advance();
self.advance();
break;
}
Some(b'\n') => {
saw_newline = true;
self.advance();
}
Some(_) => {
self.advance();
}
}
}
}
_ => break,
}
}
saw_newline
}
fn skip_inter_item_separator(&mut self) -> (bool, bool) {
let mut saw_newline = self.skip_ws_and_comments();
let mut saw_comma = false;
if self.current() == Some(b',') {
saw_comma = true;
self.advance();
if self.skip_ws_and_comments() {
saw_newline = true;
}
}
(saw_newline, saw_comma)
}
fn parse_string(&mut self, quote: u8) -> Result<String> {
self.advance();
let start = self.pos;
while self.pos < self.input.len() {
let b = self.input[self.pos];
if b < 0x20 || b == 0x7F {
return Err(syntax_err!(
"literal control character 0x{:02X} in string; use an escape or a raw string",
b
));
}
if b == quote {
let s = std::str::from_utf8(&self.input[start..self.pos])
.map_err(|_| syntax_err!("Invalid UTF-8 in string"))?;
self.pos += 1;
return Ok(s.to_string());
}
if b == b'\\' {
break;
}
self.pos += 1;
}
if self.pos >= self.input.len() {
return Err(syntax_err!("Unterminated string"));
}
let mut bytes: Vec<u8> = self.input[start..self.pos].to_vec();
while self.pos < self.input.len() {
let b = self
.advance()
.ok_or_else(|| syntax_err!("Unterminated string"))?;
if b == quote {
return String::from_utf8(bytes)
.map_err(|_| syntax_err!("Invalid UTF-8 in string"));
}
if b == b'\\' {
let escaped = self
.advance()
.ok_or_else(|| syntax_err!("Incomplete escape sequence"))?;
match escaped {
b'n' => bytes.push(b'\n'),
b'r' => bytes.push(b'\r'),
b't' => bytes.push(b'\t'),
b'b' => bytes.push(0x08),
b'f' => bytes.push(0x0C),
b'\\' => bytes.push(b'\\'),
b'"' => bytes.push(b'"'),
b'\'' => bytes.push(b'\''),
b'/' => bytes.push(b'/'),
b'x' => {
let v = self.parse_hex_digits(2, "\\x")?;
bytes.push(v as u8);
}
b'u' => {
let code = self.parse_hex_digits(4, "\\u")?;
if (0xD800..=0xDFFF).contains(&code) {
return Err(syntax_err!(
"surrogate code point U+{:04X} requires a pair; \
surrogate handling is not yet implemented",
code
));
}
let c = char::from_u32(code).ok_or_else(|| {
syntax_err!("Invalid Unicode code point U+{:04X}", code)
})?;
let mut buf = [0u8; 4];
bytes.extend_from_slice(c.encode_utf8(&mut buf).as_bytes());
}
other => {
return Err(syntax_err!("Unknown escape \\{}", other as char));
}
}
} else {
bytes.push(b);
}
}
Err(syntax_err!("Unterminated string"))
}
fn parse_hex_digits(&mut self, count: usize, label: &str) -> Result<u32> {
let mut value = 0u32;
for _ in 0..count {
let h = self
.advance()
.ok_or_else(|| syntax_err!("Incomplete {} escape", label))?;
let d = (h as char)
.to_digit(16)
.ok_or_else(|| syntax_err!("Invalid hex digit in {} escape", label))?;
value = (value << 4) | d;
}
Ok(value)
}
fn parse_raw_string(&mut self) -> Result<String> {
self.advance();
let mut hash_count = 0;
while self.current() == Some(b'#') {
hash_count += 1;
self.advance();
}
if self.current() != Some(b'"') {
return Err(syntax_err!("Expected opening quote after r and # symbols in raw string"));
}
self.advance();
let start = self.pos;
while self.pos < self.input.len() {
if self.input[self.pos] == b'"' && self.pos + hash_count < self.input.len() {
let is_closing =
(1..=hash_count).all(|j| self.input.get(self.pos + j) == Some(&b'#'));
if is_closing {
let s = std::str::from_utf8(&self.input[start..self.pos])
.map_err(|_| syntax_err!("Invalid UTF-8 in raw string"))?
.to_string();
self.pos += hash_count + 1;
return Ok(s);
}
}
self.pos += 1;
}
Err(syntax_err!("Unterminated raw string"))
}
fn parse_number(&mut self) -> Result<Value> {
let negative = self.current() == Some(b'-');
if negative {
self.advance();
}
let radix = if self.pos + 1 < self.input.len() && self.input[self.pos] == b'0' {
match self.input[self.pos + 1] {
b'x' => Some(16u32),
b'o' => Some(8u32),
b'b' => Some(2u32),
b'X' | b'O' | b'B' => {
return Err(syntax_err!(
"uppercase radix prefix 0{} not allowed; use lowercase",
self.input[self.pos + 1] as char
));
}
_ => None,
}
} else {
None
};
let literal: String;
let mut is_float = false;
if let Some(rdx) = radix {
self.advance(); self.advance(); literal = self.scan_radix_digits(rdx)?;
} else {
let int_part = self.scan_dec_digits()?;
let mut s = int_part;
if self.current() == Some(b'.') {
is_float = true;
s.push('.');
self.advance();
s.push_str(&self.scan_dec_digits()?);
}
if matches!(self.current(), Some(b'e') | Some(b'E')) {
is_float = true;
s.push('e');
self.advance();
if matches!(self.current(), Some(b'+') | Some(b'-')) {
s.push(self.current().unwrap() as char);
self.advance();
}
s.push_str(&self.scan_dec_digits()?);
}
literal = s;
}
if let Some(b) = self.current()
&& matches!(b, b'u' | b'i' | b'f')
&& self
.input
.get(self.pos + 1)
.copied()
.filter(|c| c.is_ascii_alphanumeric())
.is_some()
{
return Err(syntax_err!(
"number type suffix not allowed (saw '{}{}')",
b as char,
self.input[self.pos + 1] as char
));
}
let signed = if negative {
format!("-{}", literal)
} else {
literal
};
if let Some(rdx) = radix {
return parse_radix_literal(&signed, rdx);
}
if !is_float {
if let Ok(i) = signed.parse::<i64>() {
return Ok(Value::Number(Number::from(i)));
}
if let Ok(u) = signed.parse::<u64>() {
return Ok(Value::Number(Number::from(u)));
}
if let Ok(i) = signed.parse::<i128>()
&& let Some(n) = Number::from_f64(i as f64)
{
return Ok(Value::Number(n));
}
if let Ok(u) = signed.parse::<u128>()
&& let Some(n) = Number::from_f64(u as f64)
{
return Ok(Value::Number(n));
}
}
let f = signed
.parse::<f64>()
.map_err(|_| syntax_err!("could not parse number: {}", signed))?;
Number::from_f64(f)
.map(Value::Number)
.ok_or_else(|| syntax_err!("invalid number value: {}", signed))
}
fn scan_dec_digits(&mut self) -> Result<String> {
let mut s = String::new();
let mut last_was_under = false;
let mut has_digit = false;
while self.pos < self.input.len() {
let b = self.input[self.pos];
if b.is_ascii_digit() {
s.push(b as char);
last_was_under = false;
has_digit = true;
self.pos += 1;
} else if b == b'_' {
if !has_digit || last_was_under {
return Err(syntax_err!("invalid underscore placement in number"));
}
last_was_under = true;
self.pos += 1;
} else {
break;
}
}
if !has_digit {
return Err(syntax_err!("number requires at least one digit"));
}
if last_was_under {
return Err(syntax_err!("number cannot end with underscore"));
}
Ok(s)
}
fn scan_radix_digits(&mut self, radix: u32) -> Result<String> {
let mut s = String::new();
let mut last_was_under = false;
let mut has_digit = false;
while self.pos < self.input.len() {
let b = self.input[self.pos];
if (b as char).is_digit(radix) {
s.push(b as char);
last_was_under = false;
has_digit = true;
self.pos += 1;
} else if b == b'_' {
if !has_digit || last_was_under {
return Err(syntax_err!("invalid underscore placement in number"));
}
last_was_under = true;
self.pos += 1;
} else {
break;
}
}
if !has_digit {
return Err(syntax_err!("number requires at least one digit after radix prefix"));
}
if last_was_under {
return Err(syntax_err!("number cannot end with underscore"));
}
Ok(s)
}
fn parse_array(&mut self) -> Result<(Value, usize)> {
self.advance();
let mut elements = Vec::new();
self.skip_ws_and_comments();
while self.current() != Some(b']') {
if self.current().is_none() {
return Err(syntax_err!("Unterminated array"));
}
if let Some(value) = self.parse_value()? {
elements.push(value);
}
let (saw_newline, saw_comma) = self.skip_inter_item_separator();
if self.current() == Some(b']') {
break;
}
if self.current().is_none() {
return Err(syntax_err!("Unterminated array"));
}
if !saw_newline && !saw_comma {
return Err(syntax_err!("items on the same line must be separated by a comma"));
}
}
self.advance(); Ok((Value::Array(elements), self.pos))
}
fn parse_nested_object(&mut self) -> Result<(Value, usize)> {
self.advance();
let mut map = Map::new();
self.skip_ws_and_comments();
while self.current() != Some(b'}') {
if self.current().is_none() {
return Err(syntax_err!("Unterminated nested object"));
}
let key = self.parse_key()?;
self.skip_ws_and_comments();
if self.current() != Some(b'=') {
return Err(syntax_err!("Expected '=' after key in nested object"));
}
self.advance();
self.skip_ws_and_comments();
if let Some(value) = self.parse_value()? {
if map.contains_key(&key) {
return Err(JhonError::DuplicateKey {
line: self.line,
col: self.col,
key,
});
}
map.insert(key, value);
}
let (saw_newline, saw_comma) = self.skip_inter_item_separator();
if self.current() == Some(b'}') {
break;
}
if self.current().is_none() {
return Err(syntax_err!("Unterminated nested object"));
}
if !saw_newline && !saw_comma {
return Err(syntax_err!("items on the same line must be separated by a comma"));
}
}
self.advance(); Ok((Value::Object(map), self.pos))
}
fn parse_key(&mut self) -> Result<String> {
self.skip_ws_and_comments();
let quote = self.current();
if quote == Some(b'"') || quote == Some(b'\'') {
self.parse_string(quote.unwrap())
} else {
let start = self.pos;
while self.pos < self.input.len() {
let b = self.input[self.pos];
if is_key_delimiter(b) {
break;
}
self.pos += 1;
}
if start == self.pos {
return Err(syntax_err!("Empty key"));
}
let s = std::str::from_utf8(&self.input[start..self.pos])
.map_err(|_| syntax_err!("Invalid UTF-8 in bare key"))?;
Ok(s.to_string())
}
}
fn parse_value(&mut self) -> Result<Option<Value>> {
self.skip_ws_and_comments();
let c = self
.current()
.ok_or_else(|| syntax_err!("Expected value"))?;
let result = match c {
b'"' | b'\'' => Some(Value::String(self.parse_string(c)?)),
b'r' | b'R' => Some(Value::String(self.parse_raw_string()?)),
b'[' => Some(self.parse_array()?.0),
b'{' => Some(self.parse_nested_object()?.0),
b'0'..=b'9' | b'-' => Some(self.parse_number()?),
b't' | b'f' => Some(self.parse_boolean()?),
b'n' => Some(self.parse_null()?),
_ => return Err(syntax_err!("Unexpected character in value: {}", c as char)),
};
Ok(result)
}
fn parse_boolean(&mut self) -> Result<Value> {
if self.input.len() >= self.pos + 4 && &self.input[self.pos..self.pos + 4] == b"true" {
self.pos += 4;
return Ok(Value::Bool(true));
} else if self.input.len() >= self.pos + 5
&& &self.input[self.pos..self.pos + 5] == b"false"
{
self.pos += 5;
return Ok(Value::Bool(false));
}
Err(syntax_err!("Invalid boolean value"))
}
fn parse_null(&mut self) -> Result<Value> {
if self.input.len() >= self.pos + 4 && &self.input[self.pos..self.pos + 4] == b"null" {
self.pos += 4;
return Ok(Value::Null);
}
Err(syntax_err!("Invalid null value"))
}
}
#[inline]
fn parse_radix_literal(signed: &str, radix: u32) -> Result<Value> {
if let Ok(i) = i64::from_str_radix(signed, radix) {
return Ok(Value::Number(Number::from(i)));
}
if let Ok(u) = u64::from_str_radix(signed, radix) {
return Ok(Value::Number(Number::from(u)));
}
if let Ok(i) = i128::from_str_radix(signed, radix)
&& let Some(n) = Number::from_f64(i as f64)
{
return Ok(Value::Number(n));
}
if let Ok(u) = u128::from_str_radix(signed, radix)
&& let Some(n) = Number::from_f64(u as f64)
{
return Ok(Value::Number(n));
}
Err(syntax_err!("could not parse number: {}", signed))
}
fn parse_jhon_object(input: &str) -> Result<Value> {
let mut parser = Parser::new(input.as_bytes());
let mut map = Map::new();
parser.skip_ws_and_comments();
while parser.pos < parser.input.len() {
let key = parser.parse_key()?;
parser.skip_ws_and_comments();
if parser.current() != Some(b'=') {
return Err(syntax_err!("Expected '=' after key"));
}
parser.advance();
parser.skip_ws_and_comments();
if let Some(value) = parser.parse_value()? {
if map.contains_key(&key) {
return Err(JhonError::DuplicateKey {
line: parser.line,
col: parser.col,
key,
});
}
map.insert(key, value);
}
let (saw_newline, saw_comma) = parser.skip_inter_item_separator();
if parser.pos >= parser.input.len() {
break; }
if !saw_newline && !saw_comma {
return Err(syntax_err!("items on the same line must be separated by a comma"));
}
}
Ok(Value::Object(map))
}
fn parse_jhon_array(input: &str) -> Result<Value> {
let mut parser = Parser::new(input.as_bytes());
let mut elements = Vec::new();
parser.skip_ws_and_comments();
while parser.pos < parser.input.len() {
if parser.current() == Some(b'=') {
return Err(syntax_err!("Cannot mix key=value pairs and bare values at top level"));
}
if let Some(value) = parser.parse_value()? {
elements.push(value);
}
let (saw_newline, saw_comma) = parser.skip_inter_item_separator();
if parser.pos >= parser.input.len() {
break;
}
if !saw_newline && !saw_comma {
return Err(syntax_err!("items on the same line must be separated by a comma"));
}
}
Ok(Value::Array(elements))
}
#[inline(always)]
fn serialize_compact(value: &Value, result: &mut String) {
match value {
Value::Object(map) if map.is_empty() => {}
Value::Object(map) => serialize_object_compact(map, result),
Value::Array(arr) if arr.is_empty() => result.push_str("[]"),
Value::Array(arr) => serialize_array_compact(arr, result),
Value::String(s) => serialize_string(s, result),
Value::Number(n) => serialize_number(n, result),
Value::Bool(b) => result.push_str(if *b { "true" } else { "false" }),
Value::Null => result.push_str("null"),
}
}
#[inline(always)]
fn serialize_object_compact(map: &Map<String, Value>, result: &mut String) {
let mut first = true;
for (key, value) in map {
if !first {
result.push(',');
}
first = false;
serialize_key(key, result);
result.push('=');
match value {
Value::Object(inner) if inner.is_empty() => result.push_str("{}"),
Value::Object(inner) => {
result.push('{');
serialize_object_compact(inner, result);
result.push('}');
}
_ => serialize_compact(value, result),
}
}
}
#[inline(always)]
fn serialize_array_compact(arr: &[Value], result: &mut String) {
result.push('[');
serialize_array_contents_compact(arr, result);
result.push(']');
}
#[inline(always)]
fn serialize_array_contents_compact(arr: &[Value], result: &mut String) {
let mut first = true;
for value in arr {
if !first {
result.push(',');
}
first = false;
match value {
Value::Object(map) if map.is_empty() => result.push_str("{}"),
Value::Object(map) => {
result.push('{');
serialize_object_compact(map, result);
result.push('}');
}
_ => serialize_compact(value, result),
}
}
}
#[inline(always)]
fn serialize_key(key: &str, result: &mut String) {
if needs_quoting(key) {
serialize_string(key, result);
} else {
result.push_str(key);
}
}
#[inline(always)]
fn serialize_string(s: &str, result: &mut String) {
result.push('"');
let bytes = s.as_bytes();
let mut last_seg = 0;
for i in 0..bytes.len() {
let byte = bytes[i];
if ESCAPE[byte as usize] != 0 {
if i > last_seg {
let safe_part = unsafe { std::str::from_utf8_unchecked(&bytes[last_seg..i]) };
result.push_str(safe_part);
}
serialize_escape_byte(byte, result);
last_seg = i + 1;
}
}
if bytes.len() > last_seg {
let safe_part = unsafe { std::str::from_utf8_unchecked(&bytes[last_seg..]) };
result.push_str(safe_part);
}
result.push('"');
}
#[inline(always)]
fn serialize_escape_byte(byte: u8, result: &mut String) {
match ESCAPE[byte as usize] {
BB => result.push_str("\\b"),
TT => result.push_str("\\t"),
NN => result.push_str("\\n"),
FF => result.push_str("\\f"),
RR => result.push_str("\\r"),
QU => result.push_str("\\\""),
BS => result.push_str("\\\\"),
UU => {
const HEX: &[u8; 16] = b"0123456789abcdef";
result.push_str("\\u00");
result.push(HEX[(byte >> 4) as usize] as char);
result.push(HEX[(byte & 0x0F) as usize] as char);
}
_ => {
result.push(byte as char);
}
}
}
#[inline(always)]
fn serialize_number(n: &Number, result: &mut String) {
if let Some(i) = n.as_i64() {
let _ = write!(result, "{}", i);
} else if let Some(u) = n.as_u64() {
let _ = write!(result, "{}", u);
} else if let Some(f) = n.as_f64() {
if f.fract() == 0.0 {
let _ = write!(result, "{}", f as i64);
} else {
let _ = write!(result, "{}", f);
}
} else {
result.push('0');
}
}
fn serialize_pretty_with_depth(
value: &Value, indent: &str, depth: usize, in_array: bool, result: &mut String,
) {
match value {
Value::Object(map) if map.is_empty() => {
if in_array {
result.push_str("{}");
}
}
Value::Object(map) => serialize_object_pretty(map, indent, depth, in_array, result),
Value::Array(arr) if arr.is_empty() => result.push_str("[]"),
Value::Array(arr) => serialize_array_pretty(arr, indent, depth, result),
Value::String(s) => serialize_string(s, result),
Value::Number(n) => serialize_number(n, result),
Value::Bool(b) => result.push_str(if *b { "true" } else { "false" }),
Value::Null => result.push_str("null"),
}
}
fn serialize_object_pretty(
map: &Map<String, Value>, indent: &str, depth: usize, in_array: bool, result: &mut String,
) {
if in_array {
for _ in 0..depth + 1 {
result.push_str(indent);
}
result.push_str("{\n");
} else if depth > 0 {
result.push_str("{\n");
}
let mut first = true;
for (key, value) in map {
if !first {
result.push('\n');
}
first = false;
let inner_indent = if in_array {
depth + 2
} else if depth == 0 {
0
} else {
depth
};
for _ in 0..inner_indent {
result.push_str(indent);
}
serialize_key(key, result);
result.push_str(" = ");
serialize_pretty_with_depth(value, indent, depth + 1, false, result);
}
if in_array {
result.push('\n');
for _ in 0..depth + 1 {
result.push_str(indent);
}
result.push('}');
} else if depth > 0 {
result.push('\n');
for _ in 0..depth - 1 {
result.push_str(indent);
}
result.push('}');
}
}
fn serialize_array_pretty(arr: &[Value], indent: &str, depth: usize, result: &mut String) {
result.push_str("[\n");
serialize_array_contents_pretty(arr, indent, depth, result);
result.push('\n');
for _ in 0..depth {
result.push_str(indent);
}
result.push(']');
}
fn serialize_array_contents_pretty(arr: &[Value], indent: &str, depth: usize, result: &mut String) {
let mut first = true;
for value in arr {
if !first {
result.push('\n');
}
first = false;
if matches!(value, Value::Object(_)) {
serialize_pretty_with_depth(value, indent, depth, true, result);
} else {
for _ in 0..depth + 1 {
result.push_str(indent);
}
serialize_pretty_with_depth(value, indent, depth + 1, false, result);
}
}
}
fn serialize_pretty_inline_top(
value: &Value, indent: &str, max_inline_width: usize, result: &mut String,
) {
match value {
Value::Array(arr) if arr.is_empty() => {}
Value::Array(arr) => {
let mut first = true;
for v in arr {
if !first {
result.push('\n');
}
first = false;
render_pretty_inline(v, indent, 0, max_inline_width, result);
}
}
Value::Object(map) if map.is_empty() => {}
Value::Object(map) => {
let mut first = true;
for (k, v) in map.iter() {
if !first {
result.push('\n');
}
first = false;
serialize_key(k, result);
result.push_str(" = ");
render_pretty_inline(v, indent, 0, max_inline_width, result);
}
}
Value::Null => {}
_ => render_pretty_inline(value, indent, 0, max_inline_width, result),
}
}
fn render_pretty_inline(
value: &Value, indent: &str, depth: usize, max_inline_width: usize, result: &mut String,
) {
match value {
Value::String(s) => {
serialize_string(s, result);
return;
}
Value::Number(n) => {
serialize_number(n, result);
return;
}
Value::Bool(b) => {
result.push_str(if *b { "true" } else { "false" });
return;
}
Value::Null => {
result.push_str("null");
return;
}
Value::Object(map) if map.is_empty() => {
result.push_str("{}");
return;
}
Value::Array(arr) if arr.is_empty() => {
result.push_str("[]");
return;
}
_ => {}
}
let mut inline_buf = String::new();
push_inline(value, &mut inline_buf);
if inline_buf.len() <= max_inline_width {
result.push_str(&inline_buf);
return;
}
let mut joined_buf = String::new();
push_joined_children(value, &mut joined_buf);
if !joined_buf.is_empty() && joined_buf.len() <= max_inline_width {
let (open, close) = if matches!(value, Value::Object(_)) {
('{', '}')
} else {
('[', ']')
};
result.push(open);
result.push('\n');
push_indent(result, indent, depth + 1);
result.push_str(&joined_buf);
result.push('\n');
push_indent(result, indent, depth);
result.push(close);
return;
}
match value {
Value::Object(map) => {
result.push('{');
for (k, v) in map.iter() {
result.push('\n');
push_indent(result, indent, depth + 1);
serialize_key(k, result);
result.push_str(" = ");
render_pretty_inline(v, indent, depth + 1, max_inline_width, result);
}
result.push('\n');
push_indent(result, indent, depth);
result.push('}');
}
Value::Array(arr) => {
result.push('[');
for v in arr.iter() {
result.push('\n');
push_indent(result, indent, depth + 1);
render_pretty_inline(v, indent, depth + 1, max_inline_width, result);
}
result.push('\n');
push_indent(result, indent, depth);
result.push(']');
}
_ => unreachable!(),
}
}
fn push_indent(result: &mut String, indent: &str, n: usize) {
for _ in 0..n {
result.push_str(indent);
}
}
fn push_inline(value: &Value, out: &mut String) {
match value {
Value::Object(map) if map.is_empty() => out.push_str("{}"),
Value::Object(map) => {
out.push_str("{ ");
let mut first = true;
for (k, v) in map.iter() {
if !first {
out.push_str(", ");
}
first = false;
serialize_key(k, out);
out.push_str(" = ");
push_inline(v, out);
}
out.push_str(" }");
}
Value::Array(arr) if arr.is_empty() => out.push_str("[]"),
Value::Array(arr) => {
out.push_str("[ ");
let mut first = true;
for v in arr.iter() {
if !first {
out.push_str(", ");
}
first = false;
push_inline(v, out);
}
out.push_str(" ]");
}
Value::String(s) => serialize_string(s, out),
Value::Number(n) => serialize_number(n, out),
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Value::Null => out.push_str("null"),
}
}
fn push_joined_children(value: &Value, out: &mut String) {
match value {
Value::Object(map) => {
let mut first = true;
for (k, v) in map.iter() {
if !first {
out.push_str(", ");
}
first = false;
serialize_key(k, out);
out.push_str(" = ");
push_inline(v, out);
}
}
Value::Array(arr) => {
let mut first = true;
for v in arr.iter() {
if !first {
out.push_str(", ");
}
first = false;
push_inline(v, out);
}
}
_ => {}
}
}
fn needs_quoting(s: &str) -> bool {
if s.is_empty() {
return true;
}
s.bytes().any(is_key_delimiter)
}
fn is_key_delimiter(b: u8) -> bool {
matches!(
b,
b' ' | b'\t'
| b'\n' | b'\r'
| b'=' | b','
| b'{' | b'}'
| b'[' | b']'
| b'/' | b'"'
| b'\'' | b'#'
)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn empty_input_parses_to_null() {
assert_eq!(parse("").unwrap(), json!(null));
}
#[test]
fn whitespace_only_input_parses_to_null() {
assert_eq!(parse(" \n\t\r\n ").unwrap(), json!(null));
}
#[test]
fn comments_only_input_parses_to_null() {
assert_eq!(parse("// just a comment\n/* block */").unwrap(), json!(null));
}
#[test]
fn top_level_object_without_braces() {
assert_eq!(parse(r#"name="x",port=80"#).unwrap(), json!({"name": "x", "port": 80}));
}
#[test]
fn top_level_object_with_braces_is_single_element_array() {
assert_eq!(parse(r#"{name="x",port=80}"#).unwrap(), json!([{"name": "x", "port": 80}]));
}
#[test]
fn top_level_explicit_array_is_single_element_array() {
assert_eq!(parse("[1, 2, 3]").unwrap(), json!([[1, 2, 3]]));
}
#[test]
fn top_level_scalar_number() {
assert_eq!(parse("42").unwrap(), json!([42]));
}
#[test]
fn top_level_scalar_string() {
assert_eq!(parse(r#""hello""#).unwrap(), json!(["hello"]));
}
#[test]
fn top_level_scalar_boolean() {
assert_eq!(parse("true").unwrap(), json!([true]));
assert_eq!(parse("false").unwrap(), json!([false]));
}
#[test]
fn top_level_scalar_null() {
assert_eq!(parse("null").unwrap(), json!([null]));
}
#[test]
fn top_level_multiple_scalars_newline_separated() {
assert_eq!(parse("1\n2\n3").unwrap(), json!([1, 2, 3]));
}
#[test]
fn top_level_multiple_scalars_comma_separated() {
assert_eq!(parse(r#"1,2,"haha""#).unwrap(), json!([1, 2, "haha"]));
}
#[test]
fn top_level_mixed_scalars_and_object() {
assert_eq!(parse("1\n2\n\"haha\"\n{a=4}").unwrap(), json!([1, 2, "haha", {"a": 4}]));
}
#[test]
fn top_level_multiple_objects() {
assert_eq!(parse("{a=1}\n{b=2}").unwrap(), json!([{"a": 1}, {"b": 2}]));
}
#[test]
fn top_level_keyword_as_key_is_object_mode() {
assert_eq!(parse("true=1").unwrap(), json!({"true": 1}));
}
#[test]
fn top_level_numeric_key_is_object_mode() {
assert_eq!(parse("42=\"x\"").unwrap(), json!({"42": "x"}));
}
#[test]
fn top_level_quoted_string_key_is_object_mode() {
assert_eq!(parse(r#""key"="value""#).unwrap(), json!({"key": "value"}));
}
#[test]
fn top_level_mixed_pair_then_scalar_is_error() {
assert!(parse("a=1\n2").is_err());
}
#[test]
fn top_level_mixed_scalar_then_pair_is_error() {
assert!(parse("1\na=2").is_err());
}
#[test]
fn top_level_array_then_pair_is_error() {
assert!(parse("[1, 2] key=value").is_err());
}
#[test]
fn serialize_top_level_array_compact() {
assert_eq!(serialize(&json!([1, 2, "haha"])), r#"1,2,"haha""#);
}
#[test]
fn serialize_top_level_array_pretty() {
assert_eq!(serialize_pretty(&json!([1, 2, "haha"]), " "), "1\n2\n\"haha\"");
}
#[test]
fn serialize_top_level_array_with_object() {
assert_eq!(serialize(&json!([1, {"a": 2}])), "1,{a=2}");
}
#[test]
fn serialize_empty_object_to_empty_string() {
assert_eq!(serialize(&json!({})), "");
}
#[test]
fn serialize_empty_array_to_empty_string() {
assert_eq!(serialize(&json!([])), "");
}
#[test]
fn serialize_top_level_null_to_empty_string() {
assert_eq!(serialize(&json!(null)), "");
}
#[test]
fn serialize_nested_null_preserved() {
assert_eq!(serialize(&json!({"a": null})), "a=null");
}
#[test]
fn serialize_nested_array_preserved() {
assert_eq!(serialize(&json!({"a": [1, 2, 3]})), "a=[1,2,3]");
}
#[test]
fn single_line_comment_trailing() {
assert_eq!(parse(r#"key="value" // trailing comment"#).unwrap(), json!({"key": "value"}));
}
#[test]
fn block_comment_inline() {
assert_eq!(parse(r#"key=/* inline */"value""#).unwrap(), json!({"key": "value"}));
}
#[test]
fn block_comment_spanning_lines() {
assert_eq!(
parse("key=/* multi\nline\ncomment */\"value\"").unwrap(),
json!({"key": "value"})
);
}
#[test]
fn unterminated_block_comment_is_error() {
assert!(parse("key=/* unterminated").is_err());
}
#[test]
fn simple_identifier_key() {
assert_eq!(parse(r#"keyname="value""#).unwrap(), json!({"keyname": "value"}));
}
#[test]
fn keyword_true_as_string_key() {
assert_eq!(parse(r#"true="yes""#).unwrap(), json!({"true": "yes"}));
}
#[test]
fn keyword_false_as_string_key() {
assert_eq!(parse(r#"false="no""#).unwrap(), json!({"false": "no"}));
}
#[test]
fn keyword_null_as_string_key() {
assert_eq!(parse(r#"null="nothing""#).unwrap(), json!({"null": "nothing"}));
}
#[test]
fn key_with_hyphen() {
assert_eq!(parse(r#"my-key="value""#).unwrap(), json!({"my-key": "value"}));
}
#[test]
fn key_with_underscore_and_digits() {
assert_eq!(parse(r#"key_1="value""#).unwrap(), json!({"key_1": "value"}));
}
#[test]
fn key_with_dot() {
assert_eq!(parse("app.version=1").unwrap(), json!({"app.version": 1}));
}
#[test]
fn unicode_key() {
assert_eq!(parse(r#"名前="テスト""#).unwrap(), json!({"名前": "テスト"}));
}
#[test]
fn quoted_key_with_spaces() {
assert_eq!(parse(r#""quoted key"="value""#).unwrap(), json!({"quoted key": "value"}));
}
#[test]
fn empty_key_is_error() {
assert!(parse("=value").is_err());
}
#[test]
fn double_quoted_string() {
assert_eq!(parse(r#"key="hello""#).unwrap(), json!({"key": "hello"}));
}
#[test]
fn single_quoted_string() {
assert_eq!(parse(r#"key='hello'"#).unwrap(), json!({"key": "hello"}));
}
#[test]
fn string_escape_newline_and_tab() {
assert_eq!(
parse(r#"key="line1\nline2\tindented""#).unwrap(),
json!({"key": "line1\nline2\tindented"})
);
}
#[test]
fn string_escape_unicode() {
assert_eq!(parse(r#"key="é""#).unwrap(), json!({"key": "é"}));
}
#[test]
fn string_escape_quote_and_backslash() {
assert_eq!(parse(r#"key="a\"b\\c""#).unwrap(), json!({"key": "a\"b\\c"}));
}
#[test]
fn raw_string_basic() {
assert_eq!(parse(r###"key=r"C:\path""###).unwrap(), json!({"key": r"C:\path"}));
}
#[test]
fn raw_string_with_hashes() {
let result = parse(r###"key=r#"contains "quotes""#"###).unwrap();
assert_eq!(result["key"], r#"contains "quotes""#);
}
#[test]
fn raw_string_multiline() {
let result = parse("key=r\"line1\nline2\"").unwrap();
assert_eq!(result["key"], "line1\nline2");
}
#[test]
fn unrecognized_escape_is_error() {
assert!(parse(r#"key="\q""#).is_err());
}
#[test]
fn literal_newline_in_regular_string_is_error() {
assert!(parse("key=\"line1\nline2\"").is_err());
}
#[test]
fn unterminated_string_is_error() {
assert!(parse(r#"key="unterminated"#).is_err());
}
#[test]
fn decimal_integer() {
assert_eq!(parse("n=42").unwrap(), json!({"n": 42}));
}
#[test]
fn negative_integer() {
assert_eq!(parse("n=-5").unwrap(), json!({"n": -5}));
}
#[test]
fn number_with_underscores() {
assert_eq!(parse("n=1_000_000").unwrap(), json!({"n": 1000000}));
}
#[test]
fn negative_number_with_underscores() {
assert_eq!(parse("n=-50_000").unwrap(), json!({"n": -50000}));
}
#[test]
fn float_fractional() {
assert_eq!(parse("n=12.5").unwrap(), json!({"n": 12.5}));
}
#[test]
fn negative_float() {
assert_eq!(parse("n=-45.67").unwrap(), json!({"n": -45.67}));
}
#[test]
fn float_with_exponent_only() {
assert_eq!(parse("n=1e10").unwrap(), json!({"n": 1e10}));
}
#[test]
fn float_with_fractional_and_exponent() {
assert_eq!(parse("n=1.5E-3").unwrap(), json!({"n": 1.5e-3}));
}
#[test]
fn hex_literal_lowercase() {
assert_eq!(parse("n=0xff").unwrap(), json!({"n": 255}));
}
#[test]
fn hex_literal_uppercase_digits() {
assert_eq!(parse("n=0xDE_AD").unwrap(), json!({"n": 0xDE_AD}));
}
#[test]
fn octal_literal() {
assert_eq!(parse("n=0o777").unwrap(), json!({"n": 511}));
}
#[test]
fn binary_literal() {
assert_eq!(parse("n=0b1010").unwrap(), json!({"n": 10}));
}
#[test]
fn negative_hex_literal() {
assert_eq!(parse("n=-0xff").unwrap(), json!({"n": -255}));
}
#[test]
fn positive_with_plus_prefix_is_error() {
assert!(parse("n=+5").is_err());
}
#[test]
fn uppercase_hex_prefix_is_error() {
assert!(parse("n=0Xff").is_err());
}
#[test]
fn uppercase_octal_prefix_is_error() {
assert!(parse("n=0O77").is_err());
}
#[test]
fn uppercase_binary_prefix_is_error() {
assert!(parse("n=0B10").is_err());
}
#[test]
fn number_type_suffix_is_error() {
assert!(parse("n=5u8").is_err());
}
#[test]
fn leading_underscore_is_error() {
assert!(parse("n=_5").is_err());
}
#[test]
fn trailing_underscore_is_error() {
assert!(parse("n=5_").is_err());
}
#[test]
fn adjacent_underscores_are_error() {
assert!(parse("n=5__5").is_err());
}
#[test]
fn basic_key_value_pairs() {
assert_eq!(parse(r#"a="hello", b=123.45"#).unwrap(), json!({"a": "hello", "b": 123.45}));
}
#[test]
fn nested_object() {
assert_eq!(
parse(r#"server={host="localhost", port=8080}"#).unwrap(),
json!({"server": {"host": "localhost", "port": 8080}})
);
}
#[test]
fn whitespace_around_equals_is_insignificant() {
let a = parse(r#"name = "x""#).unwrap();
let b = parse(r#"name="x""#).unwrap();
let c = parse(r#"name ="x""#).unwrap();
let d = parse(r#"name= "x""#).unwrap();
assert_eq!(a, b);
assert_eq!(b, c);
assert_eq!(c, d);
}
#[test]
fn duplicate_keys_at_top_level_are_error() {
assert!(parse("a=1, a=2").is_err());
}
#[test]
fn duplicate_keys_in_nested_object_are_error() {
assert!(parse(r#"outer={a=1, a=2}"#).is_err());
}
#[test]
fn key_order_is_preserved() {
let result = parse(r#"z=1, a=2, m=3"#).unwrap();
let keys: Vec<&String> = result.as_object().unwrap().keys().collect();
assert_eq!(keys, vec!["z", "a", "m"]);
}
#[test]
fn same_line_comma_separated() {
assert_eq!(parse("a=1, b=2, c=3").unwrap(), json!({"a": 1, "b": 2, "c": 3}));
}
#[test]
fn newline_separated_multiline() {
assert_eq!(parse("a=1\nb=2\nc=3").unwrap(), json!({"a": 1, "b": 2, "c": 3}));
}
#[test]
fn mixed_comma_and_newline_separators() {
assert_eq!(parse("a=1,\nb=2,\nc=3").unwrap(), json!({"a": 1, "b": 2, "c": 3}));
}
#[test]
fn trailing_comma_at_top_level() {
assert_eq!(parse("a=1, b=2,").unwrap(), json!({"a": 1, "b": 2}));
}
#[test]
fn trailing_comma_in_braced_object() {
assert_eq!(parse("{a=1, b=2,}").unwrap(), json!([{"a": 1, "b": 2}]));
}
#[test]
fn trailing_comma_in_array() {
assert_eq!(parse("k=[1, 2, 3,]").unwrap()["k"], json!([1, 2, 3]));
}
#[test]
fn whitespace_around_comma_is_insignificant() {
let a = parse("a=1,b=2").unwrap();
let b = parse("a=1, b=2").unwrap();
let c = parse("a=1 ,b=2").unwrap();
let d = parse("a=1 , b=2").unwrap();
assert_eq!(a, b);
assert_eq!(b, c);
assert_eq!(c, d);
}
#[test]
fn same_line_space_only_separator_is_error() {
assert!(parse("a=1 b=2").is_err());
}
#[test]
fn same_line_tab_only_separator_is_error() {
assert!(parse("a=1\tb=2").is_err());
}
#[test]
fn array_same_line_no_commas_is_error() {
assert!(parse("k=[1 2 3]").is_err());
}
#[test]
fn empty_array() {
assert_eq!(parse("k=[]").unwrap()["k"], json!([]));
}
#[test]
fn array_of_strings() {
assert_eq!(parse(r#"k=["a", "b", "c"]"#).unwrap()["k"], json!(["a", "b", "c"]));
}
#[test]
fn array_mixed_types() {
assert_eq!(
parse(r#"k=[1, "two", true, null]"#).unwrap()["k"],
json!([1, "two", true, null])
);
}
#[test]
fn nested_arrays() {
assert_eq!(parse("k=[[1, 2], [3, 4]]").unwrap()["k"], json!([[1, 2], [3, 4]]));
}
#[test]
fn multiline_array_newline_separated() {
assert_eq!(parse("k=[\n1\n2\n3\n]").unwrap()["k"], json!([1, 2, 3]));
}
#[test]
fn unbalanced_array_is_error() {
assert!(parse("k=[1, 2").is_err());
}
#[test]
fn compact_serialize_no_spaces_around_equals() {
let value = json!({"name": "John", "age": 30});
assert_eq!(serialize(&value), r#"name="John",age=30"#);
}
#[test]
fn compact_serialize_nested_object() {
let value = json!({"server": {"host": "localhost", "port": 8080}});
assert_eq!(serialize(&value), r#"server={host="localhost",port=8080}"#);
}
#[test]
fn compact_serialize_top_level_array() {
let value = json!([{"a": 1}, {"b": 2}]);
assert_eq!(serialize(&value), r#"{a=1},{b=2}"#);
}
#[test]
fn compact_serialize_has_no_trailing_comma() {
let value = json!({"a": 1, "b": 2});
let s = serialize(&value);
assert!(!s.ends_with(','));
}
#[test]
fn pretty_serialize_spaces_around_equals_no_trailing_commas() {
let value = json!({"name": "John", "age": 30});
assert_eq!(serialize_pretty(&value, " "), "name = \"John\"\nage = 30");
}
#[test]
fn pretty_serialize_nested_object() {
let value = json!({"server": {"host": "localhost", "port": 8080}});
assert_eq!(
serialize_pretty(&value, " "),
"server = {\n host = \"localhost\"\n port = 8080\n}"
);
}
#[test]
fn pretty_serialize_deeply_nested() {
let value = json!({
"a1": [
{"b1": ["c1"]},
{"b1": ["c1", {"d1": "证件照.jpg"}]},
{"b1": ["c1"]},
{"b1": ["c1"]},
{"b1": ["c1"]},
{"b1": ["c1"]}
],
"a2": {
"b2": {
"c2": {"d2": "hahaha"},
"c3": {"d3": "hohohoh"}
}
},
"a3": [
["b4", "b5", "b6", {"c4": ["d1", "d3"]}]
]
});
let expected = r#"a1 = [
{ b1 = [ "c1" ] }
{ b1 = [ "c1", { d1 = "证件照.jpg" } ] }
{ b1 = [ "c1" ] }
{ b1 = [ "c1" ] }
{ b1 = [ "c1" ] }
{ b1 = [ "c1" ] }
]
a2 = {
b2 = {
c2 = { d2 = "hahaha" }
c3 = { d3 = "hohohoh" }
}
}
a3 = [
[
"b4", "b5", "b6", { c4 = [ "d1", "d3" ] }
]
]"#;
assert_eq!(
serialize_pretty_with_options(
&value,
&PrettyOptions {
indent: "\t".to_string(),
max_inline_width: 44,
}
),
expected
);
}
#[test]
fn pretty_serialize_array_no_trailing_commas() {
let value = json!([1, 2, 3, "hello"]);
assert_eq!(serialize_pretty(&value, " "), "1\n2\n3\n\"hello\"");
}
#[test]
fn round_trip_compact_preserves_value() {
let original = json!({"name": "John", "age": 30, "active": true});
let parsed = parse(&serialize(&original)).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn round_trip_pretty_preserves_value() {
let original = json!({"name": "John", "tags": ["a", "b"]});
let parsed = parse(&serialize_pretty(&original, " ")).unwrap();
assert_eq!(original, parsed);
}
#[test]
fn hex_octal_binary_serialize_as_decimal() {
assert_eq!(serialize(&json!({"n": 255})), "n=255");
assert_eq!(serialize(&json!({"n": 511})), "n=511");
assert_eq!(serialize(&json!({"n": 10})), "n=10");
}
#[test]
fn missing_value_is_error() {
assert!(parse("key=").is_err());
assert!(parse("key=,").is_err());
assert!(parse("a=1, key=, b=2").is_err());
}
#[test]
fn unbalanced_braces_are_error() {
assert!(parse("{a=1").is_err());
assert!(parse("a=1}").is_err());
}
#[test]
fn serde_round_trip_struct() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Person {
name: String,
age: u32,
email: String,
}
let original = Person {
name: "Alice".to_string(),
age: 25,
email: "alice@example.com".to_string(),
};
let serialized = to_string(&original).unwrap();
let deserialized: Person = from_str(&serialized).unwrap();
assert_eq!(original, deserialized);
}
#[test]
fn serde_nested_struct_round_trip() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct ServerConfig {
host: String,
port: u16,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct AppConfig {
name: String,
server: ServerConfig,
}
let config = AppConfig {
name: "MyApp".to_string(),
server: ServerConfig {
host: "localhost".to_string(),
port: 8080,
},
};
let jhon_string = to_string(&config).unwrap();
assert_eq!(jhon_string, r#"name="MyApp",server={host="localhost",port=8080}"#);
let decoded: AppConfig = from_str(&jhon_string).unwrap();
assert_eq!(config, decoded);
}
#[test]
fn serde_struct_with_arrays_round_trip() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Config {
tags: Vec<String>,
scores: Vec<i32>,
}
let config = Config {
tags: vec!["rust".to_string(), "web".to_string()],
scores: vec![100, 95, 88],
};
let jhon_string = to_string(&config).unwrap();
assert_eq!(jhon_string, r#"tags=["rust","web"],scores=[100,95,88]"#);
let decoded: Config = from_str(&jhon_string).unwrap();
assert_eq!(config, decoded);
}
#[test]
fn serde_option_some_and_none() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Config {
name: String,
description: Option<String>,
}
let with_some = Config {
name: "Test".to_string(),
description: Some("A test config".to_string()),
};
assert_eq!(to_string(&with_some).unwrap(), r#"name="Test",description="A test config""#);
let decoded: Config = from_str(r#"name="Test""#).unwrap();
assert_eq!(decoded.name, "Test");
assert_eq!(decoded.description, None);
}
#[test]
fn serde_jhon_wrapper_round_trip() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Point {
x: i32,
y: i32,
}
let point = Point { x: 10, y: 20 };
let jhon_string = Jhon::to_string(&point).unwrap();
assert_eq!(jhon_string, "x=10,y=20");
let decoded: Point = Jhon::from_str(&jhon_string).unwrap();
assert_eq!(point, decoded);
}
#[test]
fn serde_enum_representation() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
enum Status {
Active,
Inactive,
Pending,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Task {
name: String,
status: Status,
}
let task = Task {
name: "Task1".to_string(),
status: Status::Active,
};
let jhon_string = to_string(&task).unwrap();
assert_eq!(jhon_string, r#"name="Task1",status="Active""#);
let decoded: Task = from_str(&jhon_string).unwrap();
assert_eq!(decoded, task);
}
#[test]
fn serde_pretty_print_no_trailing_commas() {
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Config {
name: String,
age: u32,
}
let config = Config {
name: "John".to_string(),
age: 30,
};
assert_eq!(to_string_pretty(&config, " ").unwrap(), "name = \"John\"\nage = 30");
}
}