#![warn(
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
trivial_casts,
trivial_numeric_casts,
unsafe_code,
unstable_features,
unused_import_braces,
unused_qualifications,
rustdoc::missing_crate_level_docs,
rust_2018_idioms,
clippy::all,
clippy::pedantic,
clippy::nursery,
clippy::cargo
)]
#![allow(
clippy::suspicious_else_formatting,
clippy::match_like_matches_macro
)]
use std::char::decode_utf16;
use std::cmp::Ordering;
use std::collections::HashSet;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::mem;
use std::num::FpCategory;
use JsonError::{
Empty, EndedOnEscape, InvalidChar, InvalidCodepoint, InvalidNumber,
NonStringAsKey, UnknownEscapeSequence, UnterminatedString,
};
use JsonObject::{Array, Bool, JsonStr, Null, Number, Obj};
#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum JsonObject
{
Array(Vec<Self>),
Obj(Vec<(String, Self)>),
Number(f64),
JsonStr(String),
Bool(bool),
Null,
}
impl Eq for JsonObject {}
#[allow(clippy::derive_ord_xor_partial_ord)]
impl Ord for JsonObject
{
fn cmp(&self, other: &Self) -> Ordering
{
self.partial_cmp(other).unwrap()
}
}
impl Default for JsonObject
{
fn default() -> Self
{
Null
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum JsonError
{
Empty,
InvalidChar(char, usize),
UnterminatedString,
EndedOnEscape,
UnknownEscapeSequence(char),
NonStringAsKey,
InvalidCodepoint,
InvalidNumber,
}
impl Display for JsonError
{
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error>
{
match self
{
Empty => write!(fmt, "The input was empty (or only whitespace)"),
InvalidChar(c, num) => write!(
fmt,
"At the position {} the character {:?} was read, but it is \
invalid at this point",
c, num
),
UnterminatedString =>
{
write!(fmt, "A string with no closing quote was read")
}
EndedOnEscape => write!(fmt, "The input ended on a backslash"),
UnknownEscapeSequence(c) => write!(
fmt,
"The unknown escape sequence \'\\{}\' was encountered",
c
),
NonStringAsKey => write!(
fmt,
"Something different than a string was used as the key in an \
json object"
),
InvalidCodepoint => write!(
fmt,
"Per \'\\uXXXX\' was an invalid code point specified"
),
InvalidNumber => write!(
fmt,
"A floating-point number that is not valid in json was read \
(like NaN or Infinity)"
),
}
}
}
impl Error for JsonError {}
impl Display for JsonObject
{
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error>
{
match self
{
Array(vec) =>
{
write!(fmt, "[")?;
if !vec.is_empty()
{
write!(fmt, "{}", vec[0])?;
for val in vec.iter().skip(1)
{
write!(fmt, ", {}", val)?;
}
}
write!(fmt, "]")
}
Obj(vec) =>
{
write!(fmt, "{{")?;
if !vec.is_empty()
{
write!(fmt, "{:?}: {}", vec[0].0, vec[0].1)?;
for val in vec.iter().skip(1)
{
write!(fmt, ", {:?}: {}", val.0, val.1)?;
}
}
write!(fmt, "}}")
}
Number(val) => write!(fmt, "{}", val),
JsonStr(val) => write!(fmt, "{:?}", val),
Bool(b) => write!(fmt, "{}", b),
Null => write!(fmt, "null"),
}
}
}
macro_rules! skip_whitespace {
($s: expr, $newindex: expr) => {
while $s.len() > $newindex && $s[$newindex].is_whitespace()
{
$newindex += 1;
}
};
}
impl JsonObject
{
fn partial_read_fixed_str(
s: &[char],
newindex: usize,
goal: &str,
val: Self,
) -> Result<(Self, usize), JsonError>
{
let len = goal.len();
if s.len() < (newindex + len)
{
return Err(Empty);
}
for (i, c) in goal.chars().enumerate()
{
if s[newindex + i] != c
{
return Err(InvalidChar(s[newindex + i], newindex + i));
}
}
Ok((val, newindex + len))
}
fn partial_read_false(
s: &[char],
newindex: usize,
) -> Result<(Self, usize), JsonError>
{
Self::partial_read_fixed_str(s, newindex, "false", Bool(false))
}
fn partial_read_true(
s: &[char],
newindex: usize,
) -> Result<(Self, usize), JsonError>
{
Self::partial_read_fixed_str(s, newindex, "true", Bool(true))
}
fn partial_read_null(
s: &[char],
newindex: usize,
) -> Result<(Self, usize), JsonError>
{
Self::partial_read_fixed_str(s, newindex, "null", Null)
}
#[allow(clippy::type_complexity)]
fn partial_read_arrayoid(
s: &[char],
mut newindex: usize,
endtoken: char,
) -> Result<(Vec<Self>, usize, Vec<(usize, char)>), JsonError>
{
let mut array = vec![];
let mut delimiters = vec![];
newindex += 1;
loop
{
match Self::partial_read(s, newindex)
{
Ok((val, new)) =>
{
newindex = new;
array.push(val);
skip_whitespace!(s, newindex);
if s.len() <= newindex
{
return Err(Empty);
}
else if s[newindex] == endtoken
{
return Ok((array, newindex + 1, delimiters));
}
delimiters.push((newindex, s[newindex]));
newindex += 1;
skip_whitespace!(s, newindex);
}
Err(InvalidChar(errendtoken, errindex))
if {
skip_whitespace!(s, newindex);
errindex == newindex && errendtoken == endtoken
} =>
{
return Ok((array, newindex + 1, delimiters));
}
Err(e) => return Err(e),
}
}
}
fn partial_read_array(
s: &[char],
newindex: usize,
) -> Result<(Self, usize), JsonError>
{
match Self::partial_read_arrayoid(s, newindex, ']')
{
Ok((rv, newindex, delimiters)) =>
{
for (pos, c) in delimiters
{
if c != ','
{
return Err(InvalidChar(c, pos));
}
}
Ok((Array(rv), newindex))
}
Err(err) => Err(err),
}
}
fn partial_read_obj(
s: &[char],
newindex: usize,
) -> Result<(Self, usize), JsonError>
{
match Self::partial_read_arrayoid(s, newindex, '}')
{
Ok((mut rv, newindex, delimiters)) =>
{
let mut obj = Vec::with_capacity(delimiters.len() / 2 + 1);
for wins in delimiters.windows(2).step_by(2)
{
let ((key_pos, key_c), (val_pos, val_c)) =
(wins[0], wins[1]);
if key_c != ':'
{
return Err(InvalidChar(key_c, key_pos));
}
if val_c != ','
{
return Err(InvalidChar(val_c, val_pos));
}
}
for i in 0..rv.len()
{
if i % 2 == 1
{
match mem::replace(&mut rv[i - 1], Number(0.0))
{
JsonStr(key) =>
{
let val =
mem::replace(&mut rv[i], Number(0.0));
obj.push((key, val))
}
_ => return Err(NonStringAsKey),
}
}
}
Ok((Obj(obj), newindex))
}
Err(err) => Err(err),
}
}
fn partial_read_number(
s: &[char],
mut newindex: usize,
) -> Result<(Self, usize), JsonError>
{
skip_whitespace!(s, newindex);
let startindex = newindex;
if s.len() > newindex && (s[newindex] == '-' || s[newindex] == '+')
{
newindex += 1;
}
while s.len() > newindex && s[newindex] >= '0' && s[newindex] <= '9'
{
newindex += 1;
}
if s.len() > newindex && s[newindex] == '.'
{
newindex += 1;
while s.len() > newindex
&& s[newindex] >= '0'
&& s[newindex] <= '9'
{
newindex += 1;
}
}
if s.len() > newindex && s[newindex].to_ascii_lowercase() == 'e'
{
newindex += 1;
if s.len() > newindex
&& (s[newindex] == '-' || s[newindex] == '+')
{
newindex += 1;
}
while s.len() > newindex
&& s[newindex] >= '0'
&& s[newindex] <= '9'
{
newindex += 1;
}
}
let num: f64 = s[startindex..newindex]
.iter()
.collect::<String>()
.parse()
.expect("number parsing function called without number");
if matches!(num.classify(), FpCategory::Infinite | FpCategory::Nan)
{
Err(InvalidNumber)
}
else
{
Ok((Number(num), newindex))
}
}
#[allow(clippy::cast_possible_truncation)]
fn partial_read_string(
s: &[char],
mut newindex: usize,
) -> Result<(Self, usize), JsonError>
{
let mut rv: Vec<u16> = Vec::new();
let mut buf = [0; 2];
newindex += 1;
while s.len() > newindex && s[newindex] != '"'
{
if s[newindex] == '\\'
{
newindex += 1;
if s.len() <= newindex
{
return Err(EndedOnEscape);
}
match s[newindex]
{
'\"' => rv.extend_from_slice('\"'.encode_utf16(&mut buf)),
'\\' => rv.extend_from_slice('\\'.encode_utf16(&mut buf)),
'/' => rv.extend_from_slice('/'.encode_utf16(&mut buf)),
'b' =>
{
rv.extend_from_slice('\x08'.encode_utf16(&mut buf))
}
'f' =>
{
rv.extend_from_slice('\x0c'.encode_utf16(&mut buf))
}
'n' =>
{
rv.extend_from_slice('\x0a'.encode_utf16(&mut buf))
}
'r' =>
{
rv.extend_from_slice('\x0d'.encode_utf16(&mut buf))
}
't' =>
{
rv.extend_from_slice('\x09'.encode_utf16(&mut buf))
}
c @ ('\x00'..='\x1F') =>
{
rv.extend_from_slice(c.encode_utf16(&mut buf))
}
'u' =>
{
newindex += 1;
if s.len() <= (newindex + 4)
{
return Err(EndedOnEscape);
}
let codepoint = s[newindex..newindex + 4]
.iter()
.enumerate()
.map(|(i, c)| {
c.to_ascii_lowercase()
.to_digit(16)
.map(|v| {
(v as u16) * 16_u16.pow(3 - i as u32)
})
.ok_or(InvalidChar(*c, newindex + i))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.sum();
newindex += 3;
rv.push(codepoint);
}
c => return Err(UnknownEscapeSequence(c)),
}
}
else
{
rv.extend_from_slice(s[newindex].encode_utf16(&mut buf));
}
newindex += 1;
}
if s.len() <= newindex
{
return Err(UnterminatedString);
}
Ok((
JsonStr(
decode_utf16(rv)
.map(|x| x.map_err(|_| InvalidCodepoint))
.collect::<Result<String, _>>()?,
),
newindex + 1,
))
}
pub fn partial_read(
s: &[char],
newindex: usize,
) -> Result<(Self, usize), JsonError>
{
let mut newindex = newindex;
skip_whitespace!(s, newindex);
if s.len() <= newindex
{
return Err(Empty);
}
match s[newindex]
{
'0'..='9' | '+' | '-' => Self::partial_read_number(s, newindex),
'[' => Self::partial_read_array(s, newindex),
'{' => Self::partial_read_obj(s, newindex),
'"' => Self::partial_read_string(s, newindex),
'f' => Self::partial_read_false(s, newindex),
't' => Self::partial_read_true(s, newindex),
'n' => Self::partial_read_null(s, newindex),
_ =>
{
if s.len() >= newindex
{
Err(InvalidChar(s[newindex], newindex))
}
else
{
Err(Empty)
}
}
}
}
pub fn read(s: &str) -> Result<Self, JsonError>
{
Self::partial_read(&s.chars().collect::<Vec<_>>(), 0).map(|(x, _)| x)
}
#[must_use]
pub fn unique(&self) -> bool
{
match self
{
Array(vec) => vec.iter().all(Self::unique),
Obj(vec) =>
{
let strs_count = vec.len();
let strs = vec
.iter()
.map(|(s, obj)| {
if obj.unique()
{
Some(s)
}
else
{
None
}
})
.collect::<Option<HashSet<_>>>();
strs.map_or(false, |strs| strs_count == strs.len())
}
_ => true,
}
}
}
#[cfg(test)]
mod tests
{
use crate::JsonError::{
Empty, EndedOnEscape, InvalidChar, InvalidNumber, NonStringAsKey,
UnknownEscapeSequence, UnterminatedString,
};
use crate::JsonObject::{self, Array, Bool, JsonStr, Null, Number, Obj};
#[test]
fn display_tests()
{
let tests = vec![
(
Array(vec![
Number(42.0),
JsonStr("Hello, World!".to_string()),
]),
"[42, \"Hello, World!\"]",
),
(
Obj(vec![
("first".to_string(), Number(5.0)),
("sec".to_string(), JsonStr("val".to_string())),
("last:".to_string(), Number(-1337.0)),
]),
"{\"first\": 5, \"sec\": \"val\", \"last:\": -1337}",
),
(JsonStr("test".to_string()), "\"test\""),
(JsonStr("\"test\"".to_string()), "\"\\\"test\\\"\""),
(Bool(true), "true"),
(Bool(false), "false"),
(Null, "null"),
];
for (input, output) in tests
{
assert_eq!(format!("{}", input), output);
}
}
#[test]
fn array_read_tests()
{
let tests = vec![
("[]", "[ ]", Array(vec![])),
(
"[[[]]]",
" [[[]]]",
Array(vec![Array(vec![Array(vec![])])]),
),
(
"[[], []]",
"[ [] , []]",
Array(vec![Array(vec![]), Array(vec![])]),
),
(
"[[[[[]]]], []]",
" [ [ [ [ [ ] ] ] ] , [ ] ] ",
Array(vec![
Array(vec![Array(vec![Array(vec![Array(vec![])])])]),
Array(vec![]),
]),
),
(
"[[[[[]]]], []]",
"[[[[[]]]],[]]",
Array(vec![
Array(vec![Array(vec![Array(vec![Array(vec![])])])]),
Array(vec![]),
]),
),
];
for (input, whitespaced, output) in tests
{
assert_eq!(JsonObject::read(input).unwrap(), output);
assert_eq!(JsonObject::read(whitespaced).unwrap(), output);
assert_eq!(
format!("{}", JsonObject::read(input).unwrap()),
input
);
}
}
#[test]
fn number_read_tests()
{
let tests = vec![
("0", " -0 ", Number(0.0)),
#[warn(clippy::cast_possible_truncation)]
("10", " +10 ", Number(10.0)),
("-42", " -42", Number(-42.0)),
("10000", "10000 ", Number(10000.0)),
("5", "5 0", Number(5.0)),
("5.01", " 5.01", Number(5.01)),
("501", "5.01E+2", Number(501.0)),
("501", "5.01E2", Number(501.0)),
("-501", "-5.01E+2", Number(-501.0)),
("0.0501", "5.01E-2", Number(0.0501)),
("5.042", " 5.042", Number(5.042)),
(
"3.141592653589793",
"3.14159265358979323846264338327950288419716939937508",
Number(3.141592653589793),
),
];
for (input, whitespaced, output) in tests
{
assert_eq!(JsonObject::read(input).unwrap(), output);
assert_eq!(JsonObject::read(whitespaced).unwrap(), output);
assert_eq!(
format!("{}", JsonObject::read(input).unwrap()),
input
);
}
}
#[test]
fn read_string_tests()
{
let tests = vec![
(
"\"Hello, World!\"",
" \"Hello, World!\" ",
"Hello, World!",
),
(
"\"Test \\\" tseT\"",
"\"Test \\\" tseT\" ",
"Test \" tseT",
),
("\"deF \\\\ Abc\"", "\"deF \\\\ Abc\" ", "deF \\ Abc"),
(
"\"deF2 \\\\ 3Abc\"",
"\"deF2 \\\\ 3Abc\" ",
"deF2 \\ 3Abc",
),
("\"\\n\"", "\"\\n\"", "\n"),
("\"Json\"", "\"\\u004ason\"", "Json"),
("\"Json\"", "\"\\u004ason\"", "Json"),
("\"ä\"", "\"\\u00e4\"", "ä"),
("\"𝄞\"", "\"\\uD834\\uDD1E\"", "𝄞"),
];
for (input, whitespaced, output) in tests
{
assert_eq!(
JsonObject::read(input).unwrap(),
JsonStr(output.to_string())
);
assert_eq!(
JsonObject::read(whitespaced).unwrap(),
JsonStr(output.to_string())
);
assert_eq!(
format!("{}", JsonObject::read(input).unwrap()),
input
);
}
}
#[test]
fn read_obj_tests()
{
let longstr1 =
"{\"first\": [10, 42], \"sec\": 15, \"la\\\"st\": \"Hi, World!\"}";
let longstr2 =
"{\"first\" : [10,42],\"sec\":15,\"la\\\"st\": \"Hi, World!\" } ";
let longstr4 =
" { \"sec\" : 15 , \"la\\\"st\" : \"Hi, World!\" } ";
let tests = vec![
(
longstr1,
longstr2,
Obj(vec![
(
"first".to_string(),
Array(vec![Number(10.0), Number(42.0)]),
),
("sec".to_string(), Number(15.0)),
("la\"st".to_string(), JsonStr("Hi, World!".to_string())),
]),
),
(
"{\"sec\": 15, \"la\\\"st\": \"Hi, World!\"}",
longstr4,
Obj(vec![
("sec".to_string(), Number(15.0)),
("la\"st".to_string(), JsonStr("Hi, World!".to_string())),
]),
),
("{}", "{}", Obj(vec![])),
];
for (input, whitespaced, output) in tests
{
assert_eq!(JsonObject::read(input).unwrap(), output);
assert_eq!(JsonObject::read(whitespaced).unwrap(), output);
assert_eq!(
format!("{}", JsonObject::read(input).unwrap()),
input
);
}
}
#[test]
fn read_bool_null_tests()
{
let tests = vec![
("true", " true ", Bool(true)),
("false", "false ", Bool(false)),
("null", " null", Null),
];
for (input, whitespaced, output) in tests
{
assert_eq!(JsonObject::read(input).unwrap(), output);
assert_eq!(JsonObject::read(whitespaced).unwrap(), output);
assert_eq!(
format!("{}", JsonObject::read(input).unwrap()),
input
);
}
}
#[test]
fn read_mixed_tests()
{
let tests = vec![
(
"[5, [-4, [10, 30]]]",
"[ 5, [ -4 , [ 10 , 30 ] ]]",
Array(vec![
Number(5.0),
Array(vec![
Number(-4.0),
Array(vec![Number(10.0), Number(30.0)]),
]),
]),
),
(
"[5, \"Hi !\", -20]",
"[ 5 , \"Hi !\", -20 ]",
Array(vec![
Number(5.0),
JsonStr("Hi !".to_string()),
Number(-20.0),
]),
),
];
for (input, whitespaced, output) in tests
{
assert_eq!(JsonObject::read(input).unwrap(), output);
assert_eq!(JsonObject::read(whitespaced).unwrap(), output);
assert_eq!(
format!("{}", JsonObject::read(input).unwrap()),
input
);
}
}
#[test]
fn read_errors()
{
let tests = vec![
("", Empty),
("]", InvalidChar(']', 0)),
("[[[]]", Empty),
("[[],[]", Empty),
("[[,[]]", InvalidChar(',', 2)),
("[[].[]]", InvalidChar('.', 3)),
("[[]:[]]", InvalidChar(':', 3)),
("[[]#[]]", InvalidChar('#', 3)),
("[[] []]", InvalidChar('[', 4)),
("[[54,[]]", Empty),
("\"\\a\"", UnknownEscapeSequence('a')),
("\"\\", EndedOnEscape),
("\"", UnterminatedString),
("{\"first\":[10,42],[50]:15}", NonStringAsKey),
("fals", Empty),
("5e2147483649", InvalidNumber),
];
for (input, output) in tests
{
assert_eq!(JsonObject::read(input), Err(output));
}
}
#[test]
fn display_jsonerror_tests()
{
let tests = vec![
(
JsonObject::read(""),
"The input was empty (or only whitespace)",
),
(
JsonObject::read("{\"test\": \"abc\\uD800\"}"),
"Per \'\\uXXXX\' was an invalid code point specified",
),
(
JsonObject::read("{5: \"hgf\"}"),
"Something different than a string was used \
as the key in an json object",
),
];
for (val, s) in tests
{
assert!(val.is_err());
assert_eq!(val.unwrap_err().to_string(), s);
}
}
#[test]
fn unique_tests()
{
let tests = vec![
(Number(4.0), true),
(JsonStr("test".to_string()), true),
(Bool(true), true),
(Bool(false), true),
(Null, true),
(
Obj(vec![
("abc".to_string(), Number(0.0)),
("def".to_string(), Number(0.0)),
("ghi".to_string(), Number(5.0)),
]),
true,
),
(
Obj(vec![
("abc".to_string(), Number(0.0)),
("def".to_string(), Number(2.0)),
("abc".to_string(), Number(5.0)),
]),
false,
),
(
Array(vec![
Obj(vec![
("qwe".to_string(), JsonStr("Hi!".to_string())),
("asd".to_string(), Bool(true)),
("yxc".to_string(), Null),
]),
Obj(vec![
("rtz".to_string(), Number(0.0)),
("uio".to_string(), Number(2.0)),
("lkj".to_string(), Number(5.0)),
]),
]),
true,
),
(
Array(vec![
Obj(vec![
("qwe".to_string(), JsonStr("Hi!".to_string())),
("asd".to_string(), Bool(true)),
("yxc".to_string(), Null),
]),
Obj(vec![
("rtz".to_string(), Number(0.0)),
("uio".to_string(), Number(2.0)),
("uio".to_string(), Number(5.0)),
]),
]),
false,
),
(
Obj(vec![
(
"yse".to_string(),
Obj(vec![
("qwe".to_string(), JsonStr("Hi!".to_string())),
("asd".to_string(), Bool(true)),
("yxc".to_string(), Null),
]),
),
(
"xdr".to_string(),
Obj(vec![
("rtz".to_string(), Number(0.0)),
("uio".to_string(), Number(2.0)),
("lkj".to_string(), Number(5.0)),
]),
),
]),
true,
),
(
Obj(vec![
(
"mju".to_string(),
Obj(vec![
("qwe".to_string(), JsonStr("Hi!".to_string())),
("asd".to_string(), Bool(true)),
("yxc".to_string(), Null),
]),
),
(
"nhz".to_string(),
Obj(vec![
("rtz".to_string(), Number(0.0)),
("uio".to_string(), Number(2.0)),
("uio".to_string(), Number(5.0)),
]),
),
]),
false,
),
];
for (input, output) in tests
{
assert_eq!(input.unique(), output);
assert_eq!(JsonObject::read(&format!("{}", input)), Ok(input))
}
}
}