use serde_json::Value;
pub fn canonical_json(value: &Value) -> Value {
match value {
Value::Object(map) => {
let sorted: std::collections::BTreeMap<&String, &Value> = map.iter().collect();
Value::Object(
sorted
.into_iter()
.map(|(k, v)| (k.clone(), canonical_json(v)))
.collect(),
)
}
Value::Array(items) => Value::Array(items.iter().map(canonical_json).collect()),
other => other.clone(),
}
}
pub fn canonical_json_string(value: &Value) -> String {
serde_json::to_string(&canonical_json(value)).expect("serializing a Value cannot fail")
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_canonical_json_sorts_object_keys_recursively() {
let input = json!({"b": {"d": 1, "c": 2}, "a": 3});
let out = canonical_json(&input);
assert_eq!(canonical_json_string(&out), r#"{"a":3,"b":{"c":2,"d":1}}"#);
}
#[test]
fn test_canonical_json_preserves_array_order() {
let input = json!({"list": [3, 1, 2], "objs": [{"z": 1, "a": 2}]});
assert_eq!(
canonical_json_string(&input),
r#"{"list":[3,1,2],"objs":[{"a":2,"z":1}]}"#
);
}
#[test]
fn test_canonical_json_scalars_unchanged() {
for v in [
json!(null),
json!(true),
json!(42),
json!(-1.5),
json!("text"),
] {
assert_eq!(canonical_json(&v), v);
}
}
#[test]
fn test_canonical_json_independent_of_input_order_and_preserve_order() {
let a = json!({"x": 1, "y": {"p": 1, "q": 2}});
let b = json!({"y": {"q": 2, "p": 1}, "x": 1});
assert_eq!(canonical_json(&a), canonical_json(&b));
assert_eq!(canonical_json_string(&a), canonical_json_string(&b));
}
#[test]
fn test_canonical_json_empty_containers() {
assert_eq!(canonical_json_string(&json!({})), "{}");
assert_eq!(canonical_json_string(&json!([])), "[]");
}
#[test]
fn test_canonical_json_unicode_keys_sort_by_scalar_value() {
let input = json!({"中": 1, "a": 2});
assert_eq!(canonical_json_string(&input), r#"{"a":2,"中":1}"#);
}
}