use core::{char, fmt, str};
const MAX_DEPTH: usize = 64;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct JsonString<'a> {
encoded: &'a str,
escaped: bool,
}
impl<'a> JsonString<'a> {
pub const fn encoded(&self) -> &'a str {
self.encoded
}
pub fn as_str(&self) -> Option<&'a str> {
(!self.escaped).then_some(self.encoded)
}
pub fn decode_into<'buffer>(
&self,
buffer: &'buffer mut [u8],
) -> Result<&'buffer str, JsonError> {
if !self.escaped {
let destination = buffer
.get_mut(..self.encoded.len())
.ok_or(JsonError::BufferTooSmall)?;
destination.copy_from_slice(self.encoded.as_bytes());
return str::from_utf8(destination).map_err(|_| JsonError::InvalidUtf8);
}
let source = self.encoded.as_bytes();
let mut input = 0;
let mut output = 0;
while input < source.len() {
if source[input] != b'\\' {
let start = input;
while input < source.len() && source[input] != b'\\' {
input += 1;
}
copy_decoded(buffer, &mut output, &source[start..input])?;
continue;
}
let (character, next) = escaped_character(source, input)?;
input = next;
let mut encoded = [0; 4];
copy_decoded(
buffer,
&mut output,
character.encode_utf8(&mut encoded).as_bytes(),
)?;
}
str::from_utf8(&buffer[..output]).map_err(|_| JsonError::InvalidUtf8)
}
pub(crate) fn decoded_eq_ascii(&self, expected: &str) -> bool {
if !expected.is_ascii() {
return false;
}
if !self.escaped {
return self.encoded == expected;
}
let source = self.encoded.as_bytes();
let expected = expected.as_bytes();
let mut input = 0;
let mut output = 0;
while input < source.len() {
if source[input] == b'\\' {
let Ok((character, next)) = escaped_character(source, input) else {
return false;
};
if !character.is_ascii() || expected.get(output).copied() != Some(character as u8) {
return false;
}
input = next;
output += 1;
} else {
if expected.get(output).copied() != Some(source[input]) {
return false;
}
input += 1;
output += 1;
}
}
output == expected.len()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum JsonError {
InvalidUtf8,
InvalidSyntax,
NestingTooDeep,
InvalidEscape,
InvalidNumber,
WrongType,
MissingField(&'static str),
DuplicateField(&'static str),
BufferTooSmall,
}
impl fmt::Display for JsonError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidUtf8 => formatter.write_str("JSON is not UTF-8"),
Self::InvalidSyntax => formatter.write_str("invalid JSON syntax"),
Self::NestingTooDeep => formatter.write_str("JSON nesting is too deep"),
Self::InvalidEscape => formatter.write_str("invalid JSON string escape"),
Self::InvalidNumber => formatter.write_str("invalid JSON number"),
Self::WrongType => formatter.write_str("unexpected JSON value type"),
Self::MissingField(field) => write!(formatter, "missing JSON field {field}"),
Self::DuplicateField(field) => write!(formatter, "duplicate JSON field {field}"),
Self::BufferTooSmall => formatter.write_str("JSON string output buffer is too small"),
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct Value<'a> {
bytes: &'a [u8],
}
impl<'a> Value<'a> {
pub(crate) fn parse_document(bytes: &'a [u8]) -> Result<Self, JsonError> {
str::from_utf8(bytes).map_err(|_| JsonError::InvalidUtf8)?;
let start = whitespace(bytes, 0);
let end = parse_value(bytes, start, 0)?;
if whitespace(bytes, end) != bytes.len() {
return Err(JsonError::InvalidSyntax);
}
Ok(Self {
bytes: &bytes[start..end],
})
}
pub(crate) fn object(self) -> Result<Object<'a>, JsonError> {
if self.bytes.first() != Some(&b'{') {
return Err(JsonError::WrongType);
}
Ok(Object { bytes: self.bytes })
}
pub(crate) fn array(self) -> Result<Array<'a>, JsonError> {
if self.bytes.first() != Some(&b'[') {
return Err(JsonError::WrongType);
}
Ok(Array { bytes: self.bytes })
}
pub(crate) fn string(self) -> Result<JsonString<'a>, JsonError> {
if self.bytes.first() != Some(&b'"') || self.bytes.last() != Some(&b'"') {
return Err(JsonError::WrongType);
}
let encoded = str::from_utf8(&self.bytes[1..self.bytes.len() - 1])
.map_err(|_| JsonError::InvalidUtf8)?;
Ok(JsonString {
encoded,
escaped: encoded.as_bytes().contains(&b'\\'),
})
}
pub(crate) fn u64(self) -> Result<u64, JsonError> {
let value = str::from_utf8(self.bytes).map_err(|_| JsonError::InvalidUtf8)?;
if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(JsonError::WrongType);
}
value.parse().map_err(|_| JsonError::InvalidNumber)
}
pub(crate) fn is_null(self) -> bool {
self.bytes == b"null"
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct Object<'a> {
bytes: &'a [u8],
}
impl<'a> Object<'a> {
pub(crate) fn get(self, name: &'static str) -> Result<Option<Value<'a>>, JsonError> {
let mut found = None;
for member in self.iter() {
let (key, value) = member?;
if key.decoded_eq_ascii(name) {
if found.is_some() {
return Err(JsonError::DuplicateField(name));
}
found = Some(value);
}
}
Ok(found)
}
pub(crate) fn required(self, name: &'static str) -> Result<Value<'a>, JsonError> {
self.get(name)?.ok_or(JsonError::MissingField(name))
}
pub(crate) fn iter(self) -> ObjectIter<'a> {
ObjectIter {
bytes: self.bytes,
position: 1,
first: true,
finished: false,
}
}
}
pub(crate) struct ObjectIter<'a> {
bytes: &'a [u8],
position: usize,
first: bool,
finished: bool,
}
impl<'a> Iterator for ObjectIter<'a> {
type Item = Result<(JsonString<'a>, Value<'a>), JsonError>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
let result = self.next_inner();
if result.is_err() {
self.finished = true;
}
result.transpose()
}
}
impl<'a> ObjectIter<'a> {
fn next_inner(&mut self) -> Result<Option<(JsonString<'a>, Value<'a>)>, JsonError> {
self.position = whitespace(self.bytes, self.position);
if self.bytes.get(self.position) == Some(&b'}') {
self.finished = true;
return Ok(None);
}
if !self.first {
if self.bytes.get(self.position) != Some(&b',') {
return Err(JsonError::InvalidSyntax);
}
self.position = whitespace(self.bytes, self.position + 1);
}
self.first = false;
if self.bytes.get(self.position) != Some(&b'"') {
return Err(JsonError::InvalidSyntax);
}
let key_end = parse_string(self.bytes, self.position)?;
let key = Value {
bytes: &self.bytes[self.position..key_end],
}
.string()?;
self.position = whitespace(self.bytes, key_end);
if self.bytes.get(self.position) != Some(&b':') {
return Err(JsonError::InvalidSyntax);
}
let start = whitespace(self.bytes, self.position + 1);
let end = parse_value(self.bytes, start, 0)?;
self.position = end;
Ok(Some((
key,
Value {
bytes: &self.bytes[start..end],
},
)))
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct Array<'a> {
bytes: &'a [u8],
}
impl<'a> Array<'a> {
pub(crate) fn iter(self) -> ArrayIter<'a> {
ArrayIter {
bytes: self.bytes,
position: 1,
first: true,
finished: false,
}
}
}
pub(crate) struct ArrayIter<'a> {
bytes: &'a [u8],
position: usize,
first: bool,
finished: bool,
}
impl<'a> Iterator for ArrayIter<'a> {
type Item = Result<Value<'a>, JsonError>;
fn next(&mut self) -> Option<Self::Item> {
if self.finished {
return None;
}
let result = self.next_inner();
if result.is_err() {
self.finished = true;
}
result.transpose()
}
}
impl<'a> ArrayIter<'a> {
fn next_inner(&mut self) -> Result<Option<Value<'a>>, JsonError> {
self.position = whitespace(self.bytes, self.position);
if self.bytes.get(self.position) == Some(&b']') {
self.finished = true;
return Ok(None);
}
if !self.first {
if self.bytes.get(self.position) != Some(&b',') {
return Err(JsonError::InvalidSyntax);
}
self.position = whitespace(self.bytes, self.position + 1);
}
self.first = false;
let start = self.position;
let end = parse_value(self.bytes, start, 0)?;
self.position = end;
Ok(Some(Value {
bytes: &self.bytes[start..end],
}))
}
}
fn parse_value(bytes: &[u8], position: usize, depth: usize) -> Result<usize, JsonError> {
if depth > MAX_DEPTH {
return Err(JsonError::NestingTooDeep);
}
match bytes.get(position).copied() {
Some(b'"') => parse_string(bytes, position),
Some(b'{') => parse_object(bytes, position, depth + 1),
Some(b'[') => parse_array(bytes, position, depth + 1),
Some(b't') => literal(bytes, position, b"true"),
Some(b'f') => literal(bytes, position, b"false"),
Some(b'n') => literal(bytes, position, b"null"),
Some(b'-' | b'0'..=b'9') => parse_number(bytes, position),
_ => Err(JsonError::InvalidSyntax),
}
}
fn parse_object(bytes: &[u8], mut position: usize, depth: usize) -> Result<usize, JsonError> {
position = whitespace(bytes, position + 1);
if bytes.get(position) == Some(&b'}') {
return Ok(position + 1);
}
loop {
if bytes.get(position) != Some(&b'"') {
return Err(JsonError::InvalidSyntax);
}
position = whitespace(bytes, parse_string(bytes, position)?);
if bytes.get(position) != Some(&b':') {
return Err(JsonError::InvalidSyntax);
}
position = whitespace(
bytes,
parse_value(bytes, whitespace(bytes, position + 1), depth)?,
);
match bytes.get(position) {
Some(b'}') => return Ok(position + 1),
Some(b',') => position = whitespace(bytes, position + 1),
_ => return Err(JsonError::InvalidSyntax),
}
}
}
fn parse_array(bytes: &[u8], mut position: usize, depth: usize) -> Result<usize, JsonError> {
position = whitespace(bytes, position + 1);
if bytes.get(position) == Some(&b']') {
return Ok(position + 1);
}
loop {
position = whitespace(bytes, parse_value(bytes, position, depth)?);
match bytes.get(position) {
Some(b']') => return Ok(position + 1),
Some(b',') => position = whitespace(bytes, position + 1),
_ => return Err(JsonError::InvalidSyntax),
}
}
}
fn parse_string(bytes: &[u8], position: usize) -> Result<usize, JsonError> {
let mut index = position + 1;
while let Some(byte) = bytes.get(index).copied() {
match byte {
b'"' => return Ok(index + 1),
b'\\' => {
let (_, next) = escaped_character(bytes, index)?;
index = next;
}
0..=0x1f => return Err(JsonError::InvalidSyntax),
_ => index += 1,
}
}
Err(JsonError::InvalidSyntax)
}
fn escaped_character(bytes: &[u8], slash: usize) -> Result<(char, usize), JsonError> {
match bytes.get(slash + 1).copied() {
Some(b'"') => Ok(('"', slash + 2)),
Some(b'\\') => Ok(('\\', slash + 2)),
Some(b'/') => Ok(('/', slash + 2)),
Some(b'b') => Ok(('\u{0008}', slash + 2)),
Some(b'f') => Ok(('\u{000c}', slash + 2)),
Some(b'n') => Ok(('\n', slash + 2)),
Some(b'r') => Ok(('\r', slash + 2)),
Some(b't') => Ok(('\t', slash + 2)),
Some(b'u') => {
let first = unicode_escape(bytes, slash + 2)?;
let next = slash + 6;
let scalar = if (0xd800..=0xdbff).contains(&first) {
if bytes.get(next..next + 2) != Some(b"\\u") {
return Err(JsonError::InvalidEscape);
}
let second = unicode_escape(bytes, next + 2)?;
if !(0xdc00..=0xdfff).contains(&second) {
return Err(JsonError::InvalidEscape);
}
let high = u32::from(first - 0xd800);
let low = u32::from(second - 0xdc00);
(0x10000 + (high << 10) + low, next + 6)
} else if (0xdc00..=0xdfff).contains(&first) {
return Err(JsonError::InvalidEscape);
} else {
(u32::from(first), next)
};
let character = char::from_u32(scalar.0).ok_or(JsonError::InvalidEscape)?;
Ok((character, scalar.1))
}
_ => Err(JsonError::InvalidEscape),
}
}
fn unicode_escape(bytes: &[u8], start: usize) -> Result<u16, JsonError> {
let digits = bytes
.get(start..start + 4)
.ok_or(JsonError::InvalidEscape)?;
let mut value = 0u16;
for digit in digits {
value = value
.checked_mul(16)
.and_then(|value| hex(*digit).map(|digit| value + u16::from(digit)))
.ok_or(JsonError::InvalidEscape)?;
}
Ok(value)
}
fn hex(byte: u8) -> Option<u8> {
match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
b'A'..=b'F' => Some(byte - b'A' + 10),
_ => None,
}
}
fn parse_number(bytes: &[u8], mut position: usize) -> Result<usize, JsonError> {
if bytes.get(position) == Some(&b'-') {
position += 1;
}
match bytes.get(position) {
Some(b'0') => position += 1,
Some(b'1'..=b'9') => {
position += 1;
while matches!(bytes.get(position), Some(b'0'..=b'9')) {
position += 1;
}
}
_ => return Err(JsonError::InvalidNumber),
}
if bytes.get(position) == Some(&b'.') {
position += 1;
let start = position;
while matches!(bytes.get(position), Some(b'0'..=b'9')) {
position += 1;
}
if position == start {
return Err(JsonError::InvalidNumber);
}
}
if matches!(bytes.get(position), Some(b'e' | b'E')) {
position += 1;
if matches!(bytes.get(position), Some(b'+' | b'-')) {
position += 1;
}
let start = position;
while matches!(bytes.get(position), Some(b'0'..=b'9')) {
position += 1;
}
if position == start {
return Err(JsonError::InvalidNumber);
}
}
Ok(position)
}
fn literal(bytes: &[u8], position: usize, literal: &[u8]) -> Result<usize, JsonError> {
if bytes.get(position..position + literal.len()) == Some(literal) {
Ok(position + literal.len())
} else {
Err(JsonError::InvalidSyntax)
}
}
fn whitespace(bytes: &[u8], mut position: usize) -> usize {
while matches!(bytes.get(position), Some(b' ' | b'\n' | b'\r' | b'\t')) {
position += 1;
}
position
}
fn copy_decoded(buffer: &mut [u8], output: &mut usize, bytes: &[u8]) -> Result<(), JsonError> {
let end = output
.checked_add(bytes.len())
.ok_or(JsonError::BufferTooSmall)?;
let destination = buffer
.get_mut(*output..end)
.ok_or(JsonError::BufferTooSmall)?;
destination.copy_from_slice(bytes);
*output = end;
Ok(())
}
#[cfg(test)]
mod tests {
use std::{format, string::ToString};
use super::{parse_value, JsonError, Value, MAX_DEPTH};
#[test]
fn validates_and_iterates_nested_json() {
let value = Value::parse_document(br#" {"a":[1,{"b":true}],"skip":null} "#).unwrap();
let object = value.object().unwrap();
let array = object.required("a").unwrap().array().unwrap();
assert_eq!(array.iter().count(), 2);
assert!(object.get("missing").unwrap().is_none());
}
#[test]
fn decodes_strings_and_rejects_bad_surrogates() {
let value = Value::parse_document(br#""hello\n\u263a\ud83d\ude00""#).unwrap();
let string = value.string().unwrap();
let mut output = [0; 32];
assert_eq!(string.decode_into(&mut output).unwrap(), "hello\n☺😀");
assert_eq!(
Value::parse_document(br#""\ud83d!""#),
Err(JsonError::InvalidEscape)
);
}
#[test]
fn rejects_trailing_and_duplicate_requested_fields() {
assert!(Value::parse_document(b"{} x").is_err());
let object = Value::parse_document(br#"{"a":1,"\u0061":2}"#)
.unwrap()
.object()
.unwrap();
assert_eq!(object.get("a"), Err(JsonError::DuplicateField("a")));
}
#[test]
fn decodes_every_simple_escape_and_uppercase_hex() {
let value = Value::parse_document(br#""\"\\\/\b\f\n\r\t\u004A\u00AF""#).unwrap();
let string = value.string().unwrap();
let mut output = [0; 32];
assert_eq!(
string.decode_into(&mut output).unwrap().as_bytes(),
b"\"\\/\x08\x0c\n\r\tJ\xc2\xaf"
);
let positioned_backslash = Value::parse_document(br#""a\\b""#)
.unwrap()
.string()
.unwrap();
assert_eq!(
positioned_backslash.decode_into(&mut output).unwrap(),
"a\\b"
);
}
#[test]
fn exposes_unescaped_strings_and_checks_decoded_ascii_exactly() {
let plain = Value::parse_document(br#""plain""#)
.unwrap()
.string()
.unwrap();
assert_eq!(plain.encoded(), "plain");
assert_eq!(plain.as_str(), Some("plain"));
assert!(plain.decoded_eq_ascii("plain"));
assert!(!plain.decoded_eq_ascii("other"));
assert!(!plain.decoded_eq_ascii("£"));
let escaped = Value::parse_document(br#""\u0062""#)
.unwrap()
.string()
.unwrap();
assert_eq!(escaped.as_str(), None);
assert!(escaped.decoded_eq_ascii("b"));
assert!(!escaped.decoded_eq_ascii("a"));
}
#[test]
fn rejects_raw_control_characters_and_small_decode_buffers() {
assert_eq!(
Value::parse_document(b"\"line\nfeed\""),
Err(JsonError::InvalidSyntax)
);
let plain = Value::parse_document(br#""hello""#)
.unwrap()
.string()
.unwrap();
assert_eq!(
plain.decode_into(&mut [0; 4]),
Err(JsonError::BufferTooSmall)
);
let escaped = Value::parse_document(br#""\u263a""#)
.unwrap()
.string()
.unwrap();
assert_eq!(
escaped.decode_into(&mut [0; 2]),
Err(JsonError::BufferTooSmall)
);
}
#[test]
fn accepts_empty_containers_and_false_literal() {
assert_eq!(
Value::parse_document(b"{}")
.unwrap()
.object()
.unwrap()
.iter()
.count(),
0
);
assert_eq!(
Value::parse_document(b"[]")
.unwrap()
.array()
.unwrap()
.iter()
.count(),
0
);
assert!(Value::parse_document(b"false").is_ok());
}
#[test]
fn enforces_nesting_at_the_exact_limit() {
assert_eq!(parse_value(b"null", 0, MAX_DEPTH), Ok(4));
assert_eq!(
parse_value(b"null", 0, MAX_DEPTH + 1),
Err(JsonError::NestingTooDeep)
);
assert_eq!(
parse_value(b"null", 0, MAX_DEPTH + 2),
Err(JsonError::NestingTooDeep)
);
let arrays_at_limit = format!("{}null{}", "[".repeat(MAX_DEPTH), "]".repeat(MAX_DEPTH));
assert!(Value::parse_document(arrays_at_limit.as_bytes()).is_ok());
let arrays_too_deep = format!(
"{}null{}",
"[".repeat(MAX_DEPTH + 1),
"]".repeat(MAX_DEPTH + 1)
);
assert_eq!(
Value::parse_document(arrays_too_deep.as_bytes()),
Err(JsonError::NestingTooDeep)
);
let objects_at_limit = format!(
"{}null{}",
"{\"a\":".repeat(MAX_DEPTH),
"}".repeat(MAX_DEPTH)
);
assert!(Value::parse_document(objects_at_limit.as_bytes()).is_ok());
let objects_too_deep = format!(
"{}null{}",
"{\"a\":".repeat(MAX_DEPTH + 1),
"}".repeat(MAX_DEPTH + 1)
);
assert_eq!(
Value::parse_document(objects_too_deep.as_bytes()),
Err(JsonError::NestingTooDeep)
);
}
#[test]
fn validates_each_json_number_form() {
for number in ["0", "-1", "10", "1.25", "1e2", "1E+2", "1e-2"] {
assert!(
Value::parse_document(number.as_bytes()).is_ok(),
"number={number:?}"
);
}
for number in ["-", "01", "1.", "1e", "1e+", "1e-"] {
assert!(
Value::parse_document(number.as_bytes()).is_err(),
"number={number:?}"
);
}
assert_eq!(Value { bytes: b"" }.u64(), Err(JsonError::WrongType));
assert_eq!(Value { bytes: b"12x" }.u64(), Err(JsonError::WrongType));
assert_eq!(Value { bytes: b"42" }.u64(), Ok(42));
assert_eq!(
Value {
bytes: b"18446744073709551616"
}
.u64(),
Err(JsonError::InvalidNumber)
);
}
#[test]
fn string_conversion_checks_both_quotes() {
assert_eq!(
Value { bytes: b"plain\"" }.string(),
Err(JsonError::WrongType)
);
assert_eq!(
Value { bytes: b"\"plain" }.string(),
Err(JsonError::WrongType)
);
}
#[test]
fn formats_every_json_error() {
let cases = [
(JsonError::InvalidUtf8, "JSON is not UTF-8"),
(JsonError::InvalidSyntax, "invalid JSON syntax"),
(JsonError::NestingTooDeep, "JSON nesting is too deep"),
(JsonError::InvalidEscape, "invalid JSON string escape"),
(JsonError::InvalidNumber, "invalid JSON number"),
(JsonError::WrongType, "unexpected JSON value type"),
(JsonError::MissingField("name"), "missing JSON field name"),
(
JsonError::DuplicateField("name"),
"duplicate JSON field name",
),
(
JsonError::BufferTooSmall,
"JSON string output buffer is too small",
),
];
for (error, expected) in cases {
assert_eq!(error.to_string(), expected);
}
}
}