use crate::{Error, Expected, Item, Node, NodeKind, Result};
impl<'source> Node<'source> {
pub(crate) fn parse(s: &'source str) -> Result<Self> {
let mut iter = Iter::new(s);
let ast_root = parse_value(Item::TopLevelValue, &mut iter)?;
loop {
match iter.next() {
Some((_, ' ' | '\r' | '\n' | '\t')) => continue, Some((idx, c)) => return Err(Error::InvalidTrailingWhitespace(idx, c)),
None => return Ok(ast_root), }
}
}
}
fn parse_value<'source>(item: Item, iter: &mut Iter<'source>) -> Result<Node<'source>> {
let (idx, unexpected_char) =
match parse_value_or_closing_bracket(item, &[Expected::Value], iter)? {
ValueParseResult::Node(n) => return Ok(n),
ValueParseResult::CloseBrace(idx) => (idx, '}'),
ValueParseResult::CloseSquare(idx) => (idx, ']'),
};
Err(Error::ExpectedXsFoundY(
item,
idx,
&[Expected::Value],
unexpected_char,
))
}
fn parse_value_or_closing_bracket<'source>(
item: Item,
expected: &'static [Expected],
iter: &mut Iter<'source>,
) -> Result<ValueParseResult<'source>> {
while let Some((start_idx, c)) = iter.next() {
macro_rules! expect_ident {
($ident_name: literal => $( $chars: literal ),*) => {{
$(
match iter.next() {
Some((_, $chars)) => (),
v => return Err(Error::expected_xs_found(
Item::Literal($ident_name),
&[Expected::Char($chars)],
v
)),
}
)*
let len = $ident_name.len();
let sub_str = &iter.source[start_idx..start_idx + len];
Node {
unsplit_width: len,
kind: NodeKind::Atom(sub_str),
}
}};
}
let value_node = match c {
' ' | '\t' | '\n' | '\r' => continue, '[' => parse_array(start_idx, iter)?,
'{' => parse_object(start_idx, iter)?,
'"' => Node::new_atom(parse_string(start_idx, iter)?),
'-' => match iter.next() {
Some((_, '0')) => parse_number_after_leading_0(start_idx, iter)?,
Some((_, '1'..='9')) => parse_number_after_first_non_zero(start_idx, iter)?,
v => {
return Err(Error::expected_xs_found(
Item::Number,
&[Expected::Digit],
v,
));
}
},
'0' => parse_number_after_leading_0(start_idx, iter)?,
'1'..='9' => parse_number_after_first_non_zero(start_idx, iter)?,
'n' => expect_ident!("null" => 'u', 'l', 'l'),
't' => expect_ident!("true" => 'r', 'u', 'e'),
'f' => expect_ident!("false" => 'a', 'l', 's', 'e'),
']' => return Ok(ValueParseResult::CloseSquare(start_idx)),
'}' => return Ok(ValueParseResult::CloseBrace(start_idx)),
_ => return Err(Error::ExpectedXsFoundY(item, start_idx, expected, c)),
};
return Ok(ValueParseResult::Node(value_node));
}
Err(Error::ExpectedXsFoundEof(item, &[Expected::Value]))
}
enum ValueParseResult<'source> {
Node(Node<'source>),
CloseSquare(usize),
CloseBrace(usize),
}
fn parse_array<'source>(start_idx: usize, iter: &mut Iter<'source>) -> Result<Node<'source>> {
let first_value = match parse_value_or_closing_bracket(
Item::Array(start_idx),
&[Expected::Value, Expected::Char(']')],
iter,
)? {
ValueParseResult::CloseSquare(_) => {
return Ok(Node {
unsplit_width: "[]".len(),
kind: NodeKind::Array(vec![]),
})
}
ValueParseResult::CloseBrace(idx) => {
return Err(Error::ExpectedXsFoundY(
Item::Array(start_idx),
idx,
&[Expected::Value, Expected::Char(']')],
'}',
))
}
ValueParseResult::Node(n) => n,
};
let mut unsplit_width = "[".len() + first_value.unsplit_width + "]".len();
let mut contents = vec![first_value];
loop {
match iter.next() {
Some((_, ' ' | '\t' | '\n' | '\r')) => continue, Some((_, ']')) => {
return Ok(Node {
unsplit_width,
kind: NodeKind::Array(contents),
});
}
Some((_, ',')) => {
let n = parse_value(Item::Array(start_idx), iter)?;
unsplit_width += ", ".len() + n.unsplit_width;
contents.push(n);
}
v => {
return Err(Error::expected_xs_found(
Item::Array(start_idx),
&[Expected::Char(','), Expected::Char(']')],
v,
));
}
}
}
}
fn parse_object<'source>(start_idx: usize, iter: &mut Iter<'source>) -> Result<Node<'source>> {
let mut fields = Vec::<(&str, Node)>::new();
let mut unsplit_width = "{ ".len() + " }".len();
loop {
let key = loop {
match iter.next() {
Some((_, ' ' | '\t' | '\n' | '\r')) => continue, Some((start_idx, '"')) => break parse_string(start_idx, iter)?, Some((_, '}')) if fields.is_empty() => {
return Ok(Node {
unsplit_width: 2, kind: NodeKind::Object(vec![]),
});
}
v => {
let expected: &[Expected] = match fields.len() {
0 => &[Expected::Key, Expected::Char('}')],
_ => &[Expected::Key],
};
return Err(Error::expected_xs_found(
Item::Object(start_idx),
expected,
v,
));
}
}
};
loop {
match iter.next() {
Some((_, ' ' | '\t' | '\n' | '\r')) => continue,
Some((_, ':')) => break, v => {
return Err(Error::expected_xs_found(
Item::Object(start_idx),
&[Expected::Char(':')],
v,
));
}
}
}
let value = parse_value(Item::Object(start_idx), iter)?;
unsplit_width += key.len() + ": ".len() + value.unsplit_width;
fields.push((key, value));
loop {
match iter.next() {
Some((_, ' ' | '\t' | '\n' | '\r')) => continue,
Some((_, ',')) => break, Some((_, '}')) => {
return Ok(Node {
unsplit_width,
kind: NodeKind::Object(fields),
});
}
v => {
return Err(Error::expected_xs_found(
Item::Object(start_idx),
&[Expected::Char(','), Expected::Char('}')],
v,
));
}
}
}
unsplit_width += ", ".len(); }
}
fn parse_string<'source>(start_idx: usize, iter: &mut Iter<'source>) -> Result<&'source str> {
while let Some((idx, c)) = iter.next() {
match c {
'"' => return Ok(&iter.source[start_idx..idx + 1]),
'\\' => match iter.next() {
Some((_, '"' | '\\' | '/' | 'b' | 'f' | 'n' | 'r' | 't')) => {} Some((_, 'u')) => {
for _ in 0..4 {
match iter.next() {
Some((_, '0'..='9' | 'a'..='f' | 'A'..='F')) => {} Some((bad_idx, bad_char)) => {
return Err(Error::InvalidHexEscape(idx, bad_idx, bad_char));
}
None => return Err(Error::EofDuringString(start_idx)),
}
}
}
Some((bad_escape_idx, bad_escape_char)) => {
return Err(Error::InvalidEscape(bad_escape_idx, bad_escape_char));
}
None => return Err(Error::EofDuringString(start_idx)),
},
'\0'..='\x19' => return Err(Error::ControlCharInString(idx, c)),
_ => {} }
}
Err(Error::EofDuringString(start_idx))
}
fn parse_number_after_leading_0<'source>(
start_idx: usize,
iter: &mut Iter<'source>,
) -> Result<Node<'source>> {
Ok(match iter.peek_char() {
Some('1'..='9') => return Err(Error::LeadingZero(iter.peek_idx() - 1)),
Some('.') => {
iter.next();
parse_number_after_decimal_point(start_idx, iter)?
}
Some('e' | 'E') => {
iter.next();
parse_number_after_exponent(start_idx, iter)?
}
_ => iter.new_atom_starting_from(start_idx),
})
}
fn parse_number_after_first_non_zero<'source>(
start_idx: usize,
iter: &mut Iter<'source>,
) -> Result<Node<'source>> {
loop {
match iter.peek_char() {
Some('0'..='9') => {
iter.next(); }
Some('.') => {
iter.next();
return parse_number_after_decimal_point(start_idx, iter);
}
Some('e' | 'E') => {
iter.next();
return parse_number_after_exponent(start_idx, iter);
}
_ => return Ok(iter.new_atom_starting_from(start_idx)),
}
}
}
fn parse_number_after_decimal_point<'source>(
start_idx: usize,
iter: &mut Iter<'source>,
) -> Result<Node<'source>> {
loop {
match iter.peek_char() {
Some('0'..='9') => {
iter.next(); }
Some('.') => return Err(Error::SecondDecimalPoint(iter.peek_idx())),
Some('e' | 'E') => {
iter.next(); return parse_number_after_exponent(start_idx, iter);
}
_ => return Ok(iter.new_atom_starting_from(start_idx)),
}
}
}
fn parse_number_after_exponent<'source>(
start_idx: usize,
iter: &mut Iter<'source>,
) -> Result<Node<'source>> {
let exponent_idx = iter.peek_idx() - 1;
if let Some('+' | '-') = iter.peek_char() {
iter.next(); }
let mut has_at_least_one_digit = false;
loop {
match iter.peek_char() {
Some('0'..='9') => iter.next(), Some(c @ ('.' | 'e' | 'E' | '+' | '-')) => {
return Err(Error::InvalidCharInExponent(iter.peek_idx(), c));
}
_ => match has_at_least_one_digit {
true => return Ok(iter.new_atom_starting_from(start_idx)),
false => return Err(Error::EmptyExponent(exponent_idx)),
},
};
has_at_least_one_digit = true;
}
}
struct Iter<'source> {
inner: std::iter::Peekable<std::str::CharIndices<'source>>,
source: &'source str,
}
impl<'source> Iter<'source> {
fn new(source: &'source str) -> Self {
Self {
inner: source.char_indices().peekable(),
source,
}
}
fn peek_char(&mut self) -> Option<char> {
self.inner.peek().map(|(_idx, c)| *c)
}
fn new_atom_starting_from(&mut self, start_idx: usize) -> Node<'source> {
Node::new_atom(&self.source[start_idx..self.peek_idx()])
}
fn peek_idx(&mut self) -> usize {
self.inner
.peek()
.map_or(self.source.len(), |(idx, _c)| *idx)
}
}
impl<'source> Iterator for Iter<'source> {
type Item = (usize, char);
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
#[cfg(test)]
mod test {
use super::*;
#[track_caller]
fn check_fail(s: &str, err: Error) {
assert_eq!(Node::parse(s), Err(err));
}
#[track_caller]
fn check_atom(s: &str, literal: &str) {
assert_eq!(
Node::parse(s).expect("Parsing atom unexpectedly failed"),
Node {
unsplit_width: literal.len(),
kind: NodeKind::Atom(literal),
}
);
}
#[track_caller]
fn check_atom_no_ws(s: &str) {
check_atom(s, s);
}
#[track_caller]
fn check_ok(s: &str, exp_node: Node) {
assert_eq!(Node::parse(s), Ok(exp_node));
}
#[test]
fn unmatched_closing_bracket() {
check_fail(
"]",
Error::ExpectedXsFoundY(Item::TopLevelValue, 0, &[Expected::Value], ']'),
);
check_fail(
"}",
Error::ExpectedXsFoundY(Item::TopLevelValue, 0, &[Expected::Value], '}'),
);
check_fail(
"} ",
Error::ExpectedXsFoundY(Item::TopLevelValue, 0, &[Expected::Value], '}'),
);
check_fail(
" \t\n} ",
Error::ExpectedXsFoundY(Item::TopLevelValue, 4, &[Expected::Value], '}'),
);
}
#[test]
fn literal() {
check_atom_no_ws("true");
check_atom_no_ws("false");
check_atom_no_ws("null");
check_atom(" null", "null");
check_atom(" null\t\n ", "null");
check_fail(" null x", Error::InvalidTrailingWhitespace(10, 'x'));
}
#[test]
fn string() {
check_atom_no_ws(r#""""#);
check_atom_no_ws(r#""thing \" thing""#);
check_atom_no_ws(r#""thing \\ thing""#);
check_atom_no_ws(r#""thing \\""#);
check_atom_no_ws(r#""thing \" \\""#);
check_atom_no_ws(r#""thing \/ thing""#);
check_atom_no_ws(r#""thing \uAFFF thing""#);
check_atom_no_ws(r#""thing \u01aF thing""#);
check_fail(r#""thing \x thing""#, Error::InvalidEscape(8, 'x'));
check_fail(
r#""thing \uAFXF thing""#,
Error::InvalidHexEscape(7, 11, 'X'),
);
check_atom("\r \"\"", r#""""#);
check_atom("\"\" \r\t \n ", r#""""#);
check_atom("\r \"\" \r\t \n ", r#""""#);
check_atom("\r \"string\" \r\t \n ", r#""string""#);
check_fail(" \"\0\" x", Error::ControlCharInString(5, '\0'));
check_fail(" \"\n\" x", Error::ControlCharInString(5, '\n'));
check_fail(" \"\t\" x", Error::ControlCharInString(5, '\t'));
check_fail(
r#" "string" x"#,
Error::InvalidTrailingWhitespace(14, 'x'),
);
}
#[test]
fn number() {
check_atom_no_ws("0");
check_atom_no_ws("-0");
check_atom(" 0 \t\n ", "0");
check_fail("02", Error::LeadingZero(0));
check_fail("-02", Error::LeadingZero(1));
check_atom_no_ws("10233415216992347901");
check_atom_no_ws("0.2");
check_fail("0.2.3", Error::SecondDecimalPoint(3));
check_atom_no_ws("-0.00002");
check_atom_no_ws("0.0200000");
check_atom_no_ws("0.02e1");
check_atom_no_ws("0.02E-1201");
check_atom_no_ws("0.02e-1201");
check_atom_no_ws("0.02e+01201");
check_fail("0.02e", Error::EmptyExponent(4));
check_fail("0.02e-", Error::EmptyExponent(4));
check_atom_no_ws("0e-01"); check_atom_no_ws("0e01"); }
#[test]
fn array() {
check_ok(
"[]",
Node {
unsplit_width: 2,
kind: NodeKind::Array(vec![]),
},
);
check_ok(
" [ ]\r\t \n",
Node {
unsplit_width: 2,
kind: NodeKind::Array(vec![]),
},
);
check_fail(
" [ }\r\t \n",
Error::ExpectedXsFoundY(
Item::Array(4),
7,
&[Expected::Value, Expected::Char(']')],
'}',
),
);
check_ok(
" [ true ]\r\t \n",
Node {
unsplit_width: 6,
kind: NodeKind::Array(vec![Node::new_atom("true")]),
},
);
check_fail(
" [ true, ]\r\t \n",
Error::ExpectedXsFoundY(Item::Array(4), 12, &[Expected::Value], ']'),
);
check_ok(
" [ true, false ]\r\t \n",
Node {
unsplit_width: 13,
kind: NodeKind::Array(vec![Node::new_atom("true"), Node::new_atom("false")]),
},
);
check_ok(
" [ true, [\n\nfalse, []] ]\r\t \n",
Node {
unsplit_width: "[true, [false, []]]".len(),
kind: NodeKind::Array(vec![
Node::new_atom("true"),
Node {
unsplit_width: "[false, []]".len(),
kind: NodeKind::Array(vec![
Node::new_atom("false"),
Node {
unsplit_width: 2,
kind: NodeKind::Array(vec![]),
},
]),
},
]),
},
);
}
#[test]
fn object() {
check_ok(
"{}",
Node {
unsplit_width: "{}".len(),
kind: NodeKind::Object(vec![]),
},
);
check_ok(
" {\t \t \n\n } \t\t\n ",
Node {
unsplit_width: "{}".len(),
kind: NodeKind::Object(vec![]),
},
);
check_ok(
r#"{"key": "value"}"#,
Node {
unsplit_width: r#"{ "key": "value" }"#.len(),
kind: NodeKind::Object(vec![("\"key\"", Node::new_atom("\"value\""))]),
},
);
check_ok(
r#"{"key": "value", "key2": "value2"}"#,
Node {
unsplit_width: r#"{ "key": "value", "key2": "value2" }"#.len(),
kind: NodeKind::Object(vec![
("\"key\"", Node::new_atom("\"value\"")),
("\"key2\"", Node::new_atom("\"value2\"")),
]),
},
);
check_ok(
" {\t \t \n\n \"key\" \t:\n\n \"value\" \t\n } \t\t\n ",
Node {
unsplit_width: r#"{ "key": "value" }"#.len(),
kind: NodeKind::Object(vec![("\"key\"", Node::new_atom("\"value\""))]),
},
);
check_ok(
" {\t \t \n\n \"key\" \t:\n\n [ \"value\" \t ] \t\n } \t\t\n ",
Node {
unsplit_width: r#"{ "key": ["value"] }"#.len(),
kind: NodeKind::Object(vec![(
"\"key\"",
Node {
unsplit_width: r#"["value"]"#.len(),
kind: NodeKind::Array(vec![Node::new_atom("\"value\"")]),
},
)]),
},
);
let phat_node = Node {
unsplit_width:
r#"{ "key": "value", "key2": [{ "is_open": true }, { "is_open": false }, null] }"#
.len(),
kind: NodeKind::Object(vec![
("\"key\"", Node::new_atom("\"value\"")),
(
"\"key2\"",
Node {
unsplit_width: r#"[{ "is_open": true }, { "is_open": false }, null]"#.len(),
kind: NodeKind::Array(vec![
Node {
unsplit_width: r#"{ "is_open": true }"#.len(),
kind: NodeKind::Object(vec![(
"\"is_open\"",
Node::new_atom("true"),
)]),
},
Node {
unsplit_width: r#"{ "is_open": false }"#.len(),
kind: NodeKind::Object(vec![(
"\"is_open\"",
Node::new_atom("false"),
)]),
},
Node::new_atom("null"),
]),
},
),
]),
};
check_ok(
r#"{"key": "value", "key2": [{ "is_open": true }, { "is_open": false }, null ] }"#,
phat_node.clone(),
);
check_ok(
" {\t \t \n\n \"key\" \t:\n\n \"value\" \t\n
\t\t\r,
\"key2\": [ { \"is_open\": true }, { \"is_open\" \t: false }, null ]
} \t\t\n ",
phat_node,
);
}
#[test]
fn json_check_fail() {
check_fail(
r#"["Unclosed array""#,
Error::ExpectedXsFoundEof(Item::Array(0), &[Expected::Char(','), Expected::Char(']')]),
); check_fail(
r#"{unquoted_key: "keys must be quoted"}"#,
Error::ExpectedXsFoundY(
Item::Object(0),
1,
&[Expected::Key, Expected::Char('}')],
'u',
),
); check_fail(
r#"["extra comma",]"#,
Error::ExpectedXsFoundY(Item::Array(0), 15, &[Expected::Value], ']'),
); check_fail(
r#"["double extra comma",,]"#,
Error::ExpectedXsFoundY(Item::Array(0), 22, &[Expected::Value], ','),
); check_fail(
r#"[ , "<-- missing value"]"#,
Error::ExpectedXsFoundY(
Item::Array(0),
4,
&[Expected::Value, Expected::Char(']')],
',',
),
); check_fail(
r#"["Comma after the close"],"#,
Error::InvalidTrailingWhitespace(25, ','),
); check_fail(
r#"["Extra close"]]"#,
Error::InvalidTrailingWhitespace(15, ']'),
); check_fail(
r#"{"Extra comma": true,}"#,
Error::ExpectedXsFoundY(Item::Object(0), 21, &[Expected::Key], '}'),
);
check_fail(
r#"{"Extra value after close": true} "misplaced quoted value""#,
Error::InvalidTrailingWhitespace(34, '"'),
); check_fail(
r#"{"Illegal expression": 1 + 2}"#,
Error::ExpectedXsFoundY(
Item::Object(0),
25,
&[Expected::Char(','), Expected::Char('}')],
'+',
),
); check_fail(
r#"{"Illegal invocation": alert()}"#,
Error::ExpectedXsFoundY(Item::Object(0), 23, &[Expected::Value], 'a'),
); check_fail(
r#"{"Numbers cannot have leading zeroes": 013}"#,
Error::LeadingZero(39),
); check_fail(
r#"{"Numbers cannot be hex": 0x14}"#,
Error::ExpectedXsFoundY(
Item::Object(0),
27,
&[Expected::Char(','), Expected::Char('}')],
'x',
),
); check_fail(
r#"["Illegal backslash escape: \x15"]"#,
Error::InvalidEscape(29, 'x'),
); check_fail(
r#"[\naked]"#,
Error::ExpectedXsFoundY(
Item::Array(0),
1,
&[Expected::Value, Expected::Char(']')],
'\\',
),
); check_fail(
r#"["Illegal backslash escape: \017"]"#,
Error::InvalidEscape(29, '0'),
); check_fail(
r#"{"Missing colon" null}"#,
Error::ExpectedXsFoundY(Item::Object(0), 17, &[Expected::Char(':')], 'n'),
);
check_fail(
r#"{"Double colon":: null}"#,
Error::ExpectedXsFoundY(Item::Object(0), 16, &[Expected::Value], ':'),
); check_fail(
r#"{"Comma instead of colon", null}"#,
Error::ExpectedXsFoundY(Item::Object(0), 25, &[Expected::Char(':')], ','),
); check_fail(
r#"["Colon instead of comma": false]"#,
Error::ExpectedXsFoundY(
Item::Array(0),
25,
&[Expected::Char(','), Expected::Char(']')],
':',
),
); check_fail(
r#"["Bad value", truth]"#,
Error::ExpectedXsFoundY(Item::Literal("true"), 17, &[Expected::Char('e')], 't'),
); check_fail(
r#"['single quote']"#,
Error::ExpectedXsFoundY(
Item::Array(0),
1,
&[Expected::Value, Expected::Char(']')],
'\'',
),
); check_fail(
r#"[" tab character in string "]"#,
Error::ControlCharInString(2, '\t'),
); check_fail(
r#"["tab\ character\ in\ string\ "]"#,
Error::InvalidEscape(6, ' '),
); check_fail(
r#"["line
break"]"#,
Error::ControlCharInString(6, '\n'),
); check_fail(
r#"["line\
break"]"#,
Error::InvalidEscape(7, '\n'),
); check_fail(r#"[0e]"#, Error::EmptyExponent(2));
check_fail(r#"[0e+]"#, Error::EmptyExponent(2)); check_fail(r#"[0e+-1]"#, Error::InvalidCharInExponent(4, '-')); check_fail(
r#"{"Comma instead if closing brace": true,"#,
Error::ExpectedXsFoundEof(Item::Object(0), &[Expected::Key]),
); check_fail(
r#"["mismatch"}"#,
Error::ExpectedXsFoundY(
Item::Array(0),
11,
&[Expected::Char(','), Expected::Char(']')],
'}',
),
); }
#[test]
fn json_check_ok() {
check_ok(
r#"{
"JSON Test Pattern pass3": {
"The outermost value": "must be an object or array.",
"In this test": "It is an object."
}
}
"#,
Node {
unsplit_width: 123,
kind: NodeKind::Object(vec![(
"\"JSON Test Pattern pass3\"",
Node {
unsplit_width: 92,
kind: NodeKind::Object(vec![
(
"\"The outermost value\"",
Node::new_atom("\"must be an object or array.\""),
),
("\"In this test\"", Node::new_atom("\"It is an object.\"")),
]),
},
)]),
},
);
assert!(Node::parse(
r#"[
"JSON Test Pattern pass1",
{"object with 1 member":["array with 1 element"]},
{},
[],
-42,
true,
false,
null,
{
"integer": 1234567890,
"real": -9876.543210,
"e": 0.123456789e-12,
"E": 1.234567890E+34,
"": 23456789012E66,
"zero": 0,
"one": 1,
"space": " ",
"quote": "\"",
"backslash": "\\",
"controls": "\b\f\n\r\t",
"slash": "/ & \/",
"alpha": "abcdefghijklmnopqrstuvwyz",
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
"digit": "0123456789",
"0123456789": "digit",
"special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
"hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
"true": true,
"false": false,
"null": null,
"array":[ ],
"object":{ },
"address": "50 St. James Street",
"url": "http://www.JSON.org/",
"comment": "// /* <!-- --",
"\\# -- --> */\n ": " ",
" s p a c e d " :[1,2 , 3
,
4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7],
"jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
"quotes": "" \u0022 %22 0x22 034 "",
"\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
: "A key can be any string"
},
0.5 ,98.6
,
99.44
,
1066,
1e1,
0.1e1,
1e-1,
1e00,2e+00,2e-00
,"rosebud"]"#,
)
.is_ok()); }
}