#[derive(Debug, Clone, PartialEq)]
pub enum JsonValue {
Null,
Bool(bool),
Number(String),
String(String),
Array(Vec<JsonValue>),
Object(Vec<(String, JsonValue)>),
}
impl JsonValue {
pub fn str(s: impl Into<String>) -> JsonValue {
JsonValue::String(s.into())
}
pub fn uint(n: u64) -> JsonValue {
JsonValue::Number(n.to_string())
}
pub fn get(&self, key: &str) -> Option<&JsonValue> {
if let JsonValue::Object(members) = self {
members.iter().find(|(k, _)| k == key).map(|(_, v)| v)
} else {
None
}
}
pub fn as_str(&self) -> Option<&str> {
if let JsonValue::String(s) = self {
Some(s)
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct SerializeOptions {
pub indent: Option<usize>,
}
impl Default for SerializeOptions {
fn default() -> Self {
SerializeOptions { indent: None }
}
}
pub fn escape_string(s: &str, out: &mut String) {
out.push(0x22 as char); for c in s.chars() {
match c {
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
c if c as u32 == 0x22 => out.push_str("\\\""),
c if (c as u32) < 0x20 || (c as u32) > 0x7e => {
let cp = c as u32;
if cp > 0xffff {
let v = cp - 0x10000;
let hi = 0xd800 + (v >> 10);
let lo = 0xdc00 + (v & 0x3ff);
push_u_escape(hi, out);
push_u_escape(lo, out);
} else {
push_u_escape(cp, out);
}
}
c => out.push(c),
}
}
out.push(0x22 as char); }
fn push_u_escape(cp: u32, out: &mut String) {
const HEX: &[u8; 16] = b"0123456789abcdef";
out.push_str("\\u");
out.push(HEX[((cp >> 12) & 0xf) as usize] as char);
out.push(HEX[((cp >> 8) & 0xf) as usize] as char);
out.push(HEX[((cp >> 4) & 0xf) as usize] as char);
out.push(HEX[(cp & 0xf) as usize] as char);
}
fn write_value(v: &JsonValue, opts: &SerializeOptions, depth: usize, out: &mut String) {
let pad = |n: usize, out: &mut String| {
if let Some(w) = opts.indent {
out.push('\n');
out.push_str(&" ".repeat(w * n));
}
};
match v {
JsonValue::Null => out.push_str("null"),
JsonValue::Bool(true) => out.push_str("true"),
JsonValue::Bool(false) => out.push_str("false"),
JsonValue::Number(n) => out.push_str(n),
JsonValue::String(s) => escape_string(s, out),
JsonValue::Array(items) => {
if items.is_empty() {
out.push_str("[]");
return;
}
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
pad(depth + 1, out);
write_value(item, opts, depth + 1, out);
}
pad(depth, out);
out.push(']');
}
JsonValue::Object(members) => {
if members.is_empty() {
out.push_str("{}");
return;
}
out.push('{');
for (i, (k, val)) in members.iter().enumerate() {
if i > 0 {
out.push(',');
}
pad(depth + 1, out);
escape_string(k, out);
out.push(':');
if opts.indent.is_some() {
out.push(' ');
}
write_value(val, opts, depth + 1, out);
}
pad(depth, out);
out.push('}');
}
}
}
pub fn serialize(v: &JsonValue, opts: &SerializeOptions) -> String {
let mut out = String::new();
write_value(v, opts, 0, &mut out);
out
}
pub fn to_string(v: &JsonValue) -> String {
serialize(v, &SerializeOptions::default())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseError {
pub message: String,
pub offset: usize,
}
struct Parser<'a> {
b: &'a [u8],
i: usize,
}
impl<'a> Parser<'a> {
fn err(&self, msg: &str) -> ParseError {
ParseError { message: msg.to_string(), offset: self.i }
}
fn skip_ws(&mut self) {
while self.i < self.b.len() {
match self.b[self.i] {
b' ' | b'\t' | b'\n' | b'\r' => self.i += 1,
_ => break,
}
}
}
fn peek(&self) -> Option<u8> {
self.b.get(self.i).copied()
}
fn value(&mut self) -> Result<JsonValue, ParseError> {
self.skip_ws();
match self.peek() {
None => Err(self.err("unexpected end of input")),
Some(b'{') => self.object(),
Some(b'[') => self.array(),
Some(c) if c == 0x22 => self.string().map(JsonValue::String),
Some(b't') | Some(b'f') => self.boolean(),
Some(b'n') => self.null(),
Some(c) if c == b'-' || c.is_ascii_digit() => self.number(),
Some(_) => Err(self.err("unexpected character")),
}
}
fn expect(&mut self, c: u8) -> Result<(), ParseError> {
if self.peek() == Some(c) {
self.i += 1;
Ok(())
} else {
Err(self.err("expected a specific character"))
}
}
fn literal(&mut self, word: &[u8]) -> Result<(), ParseError> {
if self.b[self.i..].starts_with(word) {
self.i += word.len();
Ok(())
} else {
Err(self.err("invalid literal"))
}
}
fn null(&mut self) -> Result<JsonValue, ParseError> {
self.literal(b"null")?;
Ok(JsonValue::Null)
}
fn boolean(&mut self) -> Result<JsonValue, ParseError> {
if self.peek() == Some(b't') {
self.literal(b"true")?;
Ok(JsonValue::Bool(true))
} else {
self.literal(b"false")?;
Ok(JsonValue::Bool(false))
}
}
fn number(&mut self) -> Result<JsonValue, ParseError> {
let start = self.i;
if self.peek() == Some(b'-') {
self.i += 1;
}
let int_start = self.i;
while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
self.i += 1;
}
if self.i == int_start {
return Err(self.err("number missing integer part"));
}
if self.i - int_start > 1 && self.b[int_start] == b'0' {
return Err(self.err("leading zero in number"));
}
if self.peek() == Some(b'.') {
self.i += 1;
let frac_start = self.i;
while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
self.i += 1;
}
if self.i == frac_start {
return Err(self.err("number missing fraction digits"));
}
}
if matches!(self.peek(), Some(b'e') | Some(b'E')) {
self.i += 1;
if matches!(self.peek(), Some(b'+') | Some(b'-')) {
self.i += 1;
}
let exp_start = self.i;
while matches!(self.peek(), Some(c) if c.is_ascii_digit()) {
self.i += 1;
}
if self.i == exp_start {
return Err(self.err("number missing exponent digits"));
}
}
let text = core::str::from_utf8(&self.b[start..self.i])
.map_err(|_| self.err("non-utf8 in number"))?;
Ok(JsonValue::Number(text.to_string()))
}
fn string(&mut self) -> Result<String, ParseError> {
self.expect(0x22)?;
let mut s = String::new();
loop {
let c = self.peek().ok_or_else(|| self.err("unterminated string"))?;
self.i += 1;
match c {
x if x == 0x22 => return Ok(s),
b'\\' => {
let e = self.peek().ok_or_else(|| self.err("unterminated escape"))?;
self.i += 1;
match e {
x if x == 0x22 => s.push(0x22 as char),
b'\\' => s.push('\\'),
b'/' => s.push('/'),
b'b' => s.push('\u{08}'),
b'f' => s.push('\u{0c}'),
b'n' => s.push('\n'),
b'r' => s.push('\r'),
b't' => s.push('\t'),
b'u' => {
let cp = self.hex4()?;
if (0xd800..=0xdbff).contains(&cp) {
if self.peek() != Some(b'\\') {
return Err(self.err("lone high surrogate"));
}
self.i += 1;
if self.peek() != Some(b'u') {
return Err(self.err("lone high surrogate"));
}
self.i += 1;
let lo = self.hex4()?;
if !(0xdc00..=0xdfff).contains(&lo) {
return Err(self.err("invalid low surrogate"));
}
let combined = 0x10000 + ((cp - 0xd800) << 10) + (lo - 0xdc00);
s.push(
char::from_u32(combined)
.ok_or_else(|| self.err("invalid surrogate pair"))?,
);
} else if (0xdc00..=0xdfff).contains(&cp) {
return Err(self.err("lone low surrogate"));
} else {
s.push(
char::from_u32(cp)
.ok_or_else(|| self.err("invalid code point"))?,
);
}
}
_ => return Err(self.err("invalid escape")),
}
}
x if x < 0x20 => return Err(self.err("unescaped control in string")),
_ => {
self.i -= 1;
let rest = &self.b[self.i..];
let ch_str = match core::str::from_utf8(rest) {
Ok(valid) => valid,
Err(e) if e.valid_up_to() > 0 => {
core::str::from_utf8(&rest[..e.valid_up_to()]).unwrap()
}
Err(_) => return Err(self.err("invalid utf-8 in string")),
};
let ch = ch_str
.chars()
.next()
.ok_or_else(|| self.err("invalid utf-8 in string"))?;
self.i += ch.len_utf8();
s.push(ch);
}
}
}
}
fn hex4(&mut self) -> Result<u32, ParseError> {
let mut v = 0u32;
for _ in 0..4 {
let c = self.peek().ok_or_else(|| self.err("short \\u escape"))?;
let d = match c {
b'0'..=b'9' => (c - b'0') as u32,
b'a'..=b'f' => (c - b'a' + 10) as u32,
b'A'..=b'F' => (c - b'A' + 10) as u32,
_ => return Err(self.err("invalid hex digit in \\u escape")),
};
v = v * 16 + d;
self.i += 1;
}
Ok(v)
}
fn array(&mut self) -> Result<JsonValue, ParseError> {
self.expect(b'[')?;
let mut items = Vec::new();
self.skip_ws();
if self.peek() == Some(b']') {
self.i += 1;
return Ok(JsonValue::Array(items));
}
loop {
let v = self.value()?;
items.push(v);
self.skip_ws();
match self.peek() {
Some(b',') => {
self.i += 1;
}
Some(b']') => {
self.i += 1;
return Ok(JsonValue::Array(items));
}
_ => return Err(self.err("expected ',' or ']' in array")),
}
}
}
fn object(&mut self) -> Result<JsonValue, ParseError> {
self.expect(b'{')?;
let mut members: Vec<(String, JsonValue)> = Vec::new();
self.skip_ws();
if self.peek() == Some(b'}') {
self.i += 1;
return Ok(JsonValue::Object(members));
}
loop {
self.skip_ws();
if self.peek() != Some(0x22) {
return Err(self.err("expected string key in object"));
}
let key = self.string()?;
self.skip_ws();
self.expect(b':')?;
let v = self.value()?;
members.push((key, v));
self.skip_ws();
match self.peek() {
Some(b',') => {
self.i += 1;
}
Some(b'}') => {
self.i += 1;
return Ok(JsonValue::Object(members));
}
_ => return Err(self.err("expected ',' or '}' in object")),
}
}
}
}
pub fn parse(input: &str) -> Result<JsonValue, ParseError> {
let mut p = Parser { b: input.as_bytes(), i: 0 };
let v = p.value()?;
p.skip_ws();
if p.i != p.b.len() {
return Err(p.err("trailing data after JSON value"));
}
Ok(v)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serialize_ordered_object() {
let v = JsonValue::Object(vec![
("b".into(), JsonValue::uint(2)),
("a".into(), JsonValue::str("x")),
]);
assert_eq!(to_string(&v), "{\"b\":2,\"a\":\"x\"}");
}
#[test]
fn serialize_escapes() {
let v = JsonValue::str("a\"b\\c\n\t");
assert_eq!(to_string(&v), "\"a\\\"b\\\\c\\n\\t\"");
let v2 = JsonValue::str("\u{00e9}"); assert_eq!(to_string(&v2), "\"\\u00e9\"");
}
#[test]
fn roundtrip_parse_serialize() {
let src = "{\"r\":\"REC\",\"fields\":{\"A\":\"hi\",\"N\":12.50},\"arr\":[1,2,null,true]}";
let v = parse(src).unwrap();
assert_eq!(to_string(&v), src);
}
#[test]
fn fail_closed_trailing() {
assert!(parse("{} junk").is_err());
assert!(parse("[1,2,]").is_err());
assert!(parse("\"abc").is_err());
assert!(parse("01").is_err()); assert!(parse("nul").is_err());
}
#[test]
fn parse_unicode_escape() {
let v = parse("\"\\u00e9\"").unwrap();
assert_eq!(v.as_str(), Some("\u{00e9}"));
}
}