use serde_json::{Map, Value};
pub fn sort_keys(value: &Value) -> Value {
match value {
Value::Array(items) => Value::Array(items.iter().map(sort_keys).collect()),
Value::Object(object) => {
let mut keys: Vec<&String> = object.keys().collect();
keys.sort();
let mut out = Map::new();
for key in keys {
out.insert(key.clone(), sort_keys(&object[key]));
}
Value::Object(out)
}
other => other.clone(),
}
}
pub fn canonical_json(value: &Value) -> String {
let sorted = sort_keys(value);
let mut text = pretty(&sorted, 0);
text.push('\n');
text
}
fn pretty(value: &Value, depth: usize) -> String {
let pad = |d: usize| " ".repeat(d);
match value {
Value::Array(items) if items.is_empty() => "[]".into(),
Value::Array(items) => {
let inner: Vec<String> = items
.iter()
.map(|v| format!("{}{}", pad(depth + 1), pretty(v, depth + 1)))
.collect();
format!("[\n{}\n{}]", inner.join(",\n"), pad(depth))
}
Value::Object(object) if object.is_empty() => "{}".into(),
Value::Object(object) => {
let mut keys: Vec<&String> = object.keys().collect();
keys.sort();
let inner: Vec<String> = keys
.into_iter()
.map(|k| {
format!(
"{}{}: {}",
pad(depth + 1),
serde_json::to_string(k).unwrap(),
pretty(&object[k], depth + 1)
)
})
.collect();
format!("{{\n{}\n{}}}", inner.join(",\n"), pad(depth))
}
Value::Number(n) => json_number(n),
other => serde_json::to_string(other).unwrap(),
}
}
fn json_number(n: &serde_json::Number) -> String {
if let Some(f) = n.as_f64() {
if n.is_f64() && f.fract() == 0.0 && f.abs() < 1e21 {
return format!("{}", f as i64);
}
}
n.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sorted_pretty_with_js_numbers() {
let v = serde_json::json!({"b": [1, 2.0, 2.5], "a": {"z": null, "y": "s"}});
assert_eq!(canonical_json(&v), "{\n \"a\": {\n \"y\": \"s\",\n \"z\": null\n },\n \"b\": [\n 1,\n 2,\n 2.5\n ]\n}\n");
assert_eq!(canonical_json(&serde_json::json!({})), "{}\n");
}
}