use std::cmp::Ordering;
use serde_json::Value;
pub fn to_canonical_json(value: &Value) -> String {
let mut out = String::new();
write_canonical(value, &mut out);
out
}
fn write_canonical(value: &Value, out: &mut String) {
match value {
Value::Null => out.push_str("null"),
Value::Bool(true) => out.push_str("true"),
Value::Bool(false) => out.push_str("false"),
Value::Number(n) => out.push_str(&n.to_string()),
Value::String(s) => write_json_string(s, out),
Value::Array(items) => {
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_canonical(item, out);
}
out.push(']');
}
Value::Object(map) => {
let mut keys: Vec<&String> = map.keys().collect();
keys.sort_by(|a, b| utf16_cmp(a, b));
out.push('{');
for (i, key) in keys.into_iter().enumerate() {
if i > 0 {
out.push(',');
}
write_json_string(key, out);
out.push(':');
write_canonical(&map[key], out);
}
out.push('}');
}
}
}
fn utf16_cmp(a: &str, b: &str) -> Ordering {
a.encode_utf16().cmp(b.encode_utf16())
}
fn write_json_string(s: &str, out: &mut String) {
out.push_str(&serde_json::to_string(s).expect("serializing a string to JSON cannot fail"));
}
#[cfg(test)]
mod tests {
use serde_json::{Value, json};
use super::to_canonical_json;
#[test]
fn sorts_keys_and_strips_whitespace() {
let v: Value = serde_json::from_str(r#"{ "b" : 2, "a" : [1, {"y": 0, "x": 1}] }"#)
.expect("valid JSON");
assert_eq!(to_canonical_json(&v), r#"{"a":[1,{"x":1,"y":0}],"b":2}"#);
}
#[test]
fn sorts_keys_by_utf16_code_units_not_code_points() {
let mut map = serde_json::Map::new();
map.insert("\u{ff61}".to_owned(), json!(1));
map.insert("\u{1f600}".to_owned(), json!(2));
assert_eq!(
to_canonical_json(&Value::Object(map)),
"{\"\u{1f600}\":2,\"\u{ff61}\":1}"
);
}
#[test]
fn escapes_strings_canonically() {
let v = json!({ "k": "a\"b\\c\n\u{0007}€" });
assert_eq!(to_canonical_json(&v), "{\"k\":\"a\\\"b\\\\c\\n\\u0007€\"}");
}
#[test]
fn numbers_use_serde_json_default_formatting() {
assert_eq!(to_canonical_json(&json!([1, -3, 1.5])), "[1,-3,1.5]");
let v: Value = serde_json::from_str("1.0").expect("valid JSON");
assert_eq!(to_canonical_json(&v), "1.0");
}
}