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('\"');
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('\"');
}
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(n.to_string().as_str()),
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('\n');
out.push_str(prefix);
out.push(']');
}
}
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('\n');
out.push_str(prefix);
out.push('}');
}
}
}
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(serde_json::json! {null}, "null");
a(serde_json::json! {1}, "1");
a(Value::Number(Number::from_f64(1.0).unwrap()), "1.0");
a(
Value::Number(Number::from_f64(-1.0002300e2).unwrap()),
"-100.023",
);
a(serde_json::json! {"foo"}, "\"foo\"");
a(
serde_json::json! {r#"hello "world""#},
r#""hello \"world\"""#,
);
a(
serde_json::json! {[1, 2]},
"[
1,
2
]",
);
a(
serde_json::json! {[1, 2, []]},
"[
1,
2,
[]
]",
);
a(
serde_json::json! {[1, 2, [1, 2, [1, 2]]]},
"[
1,
2,
[
1,
2,
[
1,
2
]
]
]",
);
a(
serde_json::json! {{"yo": 1, "lo": 2, "no": {}}},
"{
\"lo\": 2,
\"no\": {},
\"yo\": 1
}",
);
a(
serde_json::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(
serde_json::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';
#[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()
}