use serde::Serialize;
use serde_json::Value;
pub fn to_canonical_bytes<T: Serialize>(value: &T) -> Result<Vec<u8>, serde_json::Error> {
let json = serde_json::to_value(value)?;
let mut out = Vec::new();
write_canonical(&json, &mut out);
Ok(out)
}
pub fn to_canonical_string<T: Serialize>(value: &T) -> Result<String, serde_json::Error> {
let bytes = to_canonical_bytes(value)?;
Ok(String::from_utf8(bytes).unwrap_or_default())
}
fn write_canonical(value: &Value, out: &mut Vec<u8>) {
match value {
Value::Object(map) => {
let mut keys: Vec<&String> = map.keys().collect();
keys.sort_unstable();
out.push(b'{');
for (i, key) in keys.into_iter().enumerate() {
if i > 0 {
out.push(b',');
}
write_json_string(key, out);
out.push(b':');
write_canonical(&map[key], out);
}
out.push(b'}');
}
Value::Array(items) => {
out.push(b'[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push(b',');
}
write_canonical(item, out);
}
out.push(b']');
}
other => {
out.extend_from_slice(other.to_string().as_bytes());
}
}
}
fn write_json_string(s: &str, out: &mut Vec<u8>) {
let encoded = Value::String(s.to_owned()).to_string();
out.extend_from_slice(encoded.as_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn nested_objects_are_sorted_at_every_level() {
let v = json!({"z": {"b": 1, "a": 2}, "a": [{"y": 1, "x": 2}]});
assert_eq!(
to_canonical_string(&v).unwrap(),
r#"{"a":[{"x":2,"y":1}],"z":{"a":2,"b":1}}"#
);
}
#[test]
fn measured_precision_is_not_normalised_away() {
let coarse = json!({"magnitude": 1.5});
let fine: Value = serde_json::from_str(r#"{"magnitude": 1.50}"#).unwrap();
assert_eq!(
to_canonical_string(&coarse).unwrap(),
to_canonical_string(&fine).unwrap()
);
assert_eq!(to_canonical_string(&json!(1)).unwrap(), "1");
assert_eq!(to_canonical_string(&json!(1.0)).unwrap(), "1.0");
}
#[test]
fn strings_are_escaped_and_unicode_survives() {
let v = json!({"a": "quote\" newline\n é"});
let s = to_canonical_string(&v).unwrap();
assert!(s.contains(r#"quote\" newline\n é"#), "{s}");
let back: Value = serde_json::from_str(&s).unwrap();
assert_eq!(back, v);
}
#[test]
fn keys_sort_by_scalar_value_not_by_locale() {
let v = json!({"a": 1, "Z": 2});
assert_eq!(to_canonical_string(&v).unwrap(), r#"{"Z":2,"a":1}"#);
}
#[test]
fn an_array_is_separated_and_keeps_its_order() {
assert_eq!(to_canonical_string(&json!([])).unwrap(), "[]");
assert_eq!(to_canonical_string(&json!([1])).unwrap(), "[1]");
assert_eq!(to_canonical_string(&json!([1, 2, 3])).unwrap(), "[1,2,3]");
assert_eq!(
to_canonical_string(&json!(["b", "a", "c"])).unwrap(),
r#"["b","a","c"]"#
);
assert_eq!(
to_canonical_string(&json!([{"z": 1, "a": [2, 3]}, []])).unwrap(),
r#"[{"a":[2,3],"z":1},[]]"#
);
}
}