extern crate itertools;
#[cfg(test)]
#[macro_use]
extern crate serde_json;
#[cfg(not(test))]
extern crate serde_json;
pub fn to_json(v: &serde_json::Value) -> String {
let s = String::new();
to_json_(v, s, "")
}
fn escape_json_string(out: &mut String, s: &str) {
out.push_str("\"");
let bytes = s.as_bytes();
let mut start = 0;
for (i, &byte) in bytes.iter().enumerate() {
let escape = ESCAPE[byte as usize];
if escape == 0 {
continue;
}
if start < i {
out.push_str(&s[start..i]);
}
let char_escape = CharEscape::from_escape_table(escape, byte);
out.push_str(&write_char_escape(char_escape));
start = i + 1;
}
if start != bytes.len() {
out.push_str(&s[start..]);
}
out.push_str("\"");
}
const TAB: &str = " ";
fn to_json_(v: &serde_json::Value, mut out: String, prefix: &str) -> String {
let prefix2 = format!("{}{}", prefix, TAB);
match v {
serde_json::Value::String(s) => escape_json_string(&mut out, s),
serde_json::Value::Null => out.push_str("null"),
serde_json::Value::Bool(b) => if *b {
out.push_str("true")
} else {
out.push_str("false")
},
serde_json::Value::Number(n) => out.push_str(&format!("{}", n)),
serde_json::Value::Array(a) => {
let len = a.len();
if len == 0 {
out.push_str("[]");
} else {
out.push_str("[\n");
for (idx, item) in itertools::enumerate(a.iter()) {
out.push_str(&prefix2);
out = to_json_(item, out, &prefix2);
if idx < len - 1 {
out.push_str(",\n");
}
}
out.push_str("\n");
out.push_str(&prefix);
out.push_str("]");
}
}
serde_json::Value::Object(m) => {
let len = m.len();
if len == 0 {
out.push_str("{}");
} else {
out.push_str("{\n");
for (idx, k) in itertools::enumerate(itertools::sorted(m.keys())) {
let v = m.get(k).unwrap();
out.push_str(&prefix2);
escape_json_string(&mut out, k);
out.push_str(": ");
out = to_json_(v, out, &prefix2);
if idx < len - 1 {
out.push_str(",\n");
}
}
out.push_str("\n");
out.push_str(&prefix);
out.push_str("}");
}
}
}
out
}
#[cfg(test)]
mod tests {
use serde_json::{value::Number, Value};
fn a(v: Value, out: &'static str) {
assert_eq!(super::to_json(&v), out);
}
#[test]
fn to_json() {
a(json! {null}, "null");
a(json! {1}, "1");
a(Value::Number(Number::from_f64(1.0).unwrap()), "1");
a(
Value::Number(Number::from_f64(-1.0002300e2).unwrap()),
"-100.023",
);
a(json!{"foo"}, "\"foo\"");
a(json!{r#"hello "world""#}, r#""hello \"world\"""#);
a(
json!{[1, 2]},
"[
1,
2
]",
);
a(
json!{[1, 2, []]},
"[
1,
2,
[]
]",
);
a(
json!{[1, 2, [1, 2, [1, 2]]]},
"[
1,
2,
[
1,
2,
[
1,
2
]
]
]",
);
a(
json!{{"yo": 1, "lo": 2, "no": {}}},
"{
\"lo\": 2,
\"no\": {},
\"yo\": 1
}",
);
a(
json!{{"yo": 1, "lo": 2, "baz": {"one": 1, "do": 2, "tres": {"x": "x", "y": "y"}}}},
"{
\"baz\": {
\"do\": 2,
\"one\": 1,
\"tres\": {
\"x\": \"x\",
\"y\": \"y\"
}
},
\"lo\": 2,
\"yo\": 1
}",
);
a(
json!{{"yo": 1, "lo": 2, "baz": {"one": 1, "do": 2, "tres": ["x", "x", "y", "y"]}}},
"{
\"baz\": {
\"do\": 2,
\"one\": 1,
\"tres\": [
\"x\",
\"x\",
\"y\",
\"y\"
]
},
\"lo\": 2,
\"yo\": 1
}",
);
}
}
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 U: u8 = b'u';
#[cfg_attr(rustfmt, rustfmt_skip)]
static ESCAPE: [u8; 256] = [
U, U, U, U, U, U, U, U, BB, TT, NN, U, FF, RR, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, U, 0, 0, QU, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, BS, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
pub enum CharEscape {
Quote,
ReverseSolidus,
Solidus,
Backspace,
FormFeed,
LineFeed,
CarriageReturn,
Tab,
AsciiControl(u8),
}
impl CharEscape {
#[inline]
fn from_escape_table(escape: u8, byte: u8) -> CharEscape {
match escape {
self::BB => CharEscape::Backspace,
self::TT => CharEscape::Tab,
self::NN => CharEscape::LineFeed,
self::FF => CharEscape::FormFeed,
self::RR => CharEscape::CarriageReturn,
self::QU => CharEscape::Quote,
self::BS => CharEscape::ReverseSolidus,
self::U => CharEscape::AsciiControl(byte),
_ => unreachable!(),
}
}
}
#[inline]
fn write_char_escape(char_escape: CharEscape) -> String {
use self::CharEscape::*;
let mut out: Vec<u8> = vec![];
match char_escape {
Quote => out.extend(b"\\\""),
ReverseSolidus => out.extend(b"\\\\"),
Solidus => out.extend(b"\\/"),
Backspace => out.extend(b"\\b"),
FormFeed => out.extend(b"\\f"),
LineFeed => out.extend(b"\\n"),
CarriageReturn => out.extend(b"\\r"),
Tab => out.extend(b"\\t"),
AsciiControl(byte) => {
static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef";
let bytes = &[
b'\\',
b'u',
b'0',
b'0',
HEX_DIGITS[(byte >> 4) as usize],
HEX_DIGITS[(byte & 0xF) as usize],
];
out.extend(bytes);
}
};
String::from_utf8(out).unwrap()
}