use serde_json::Value;
pub fn canonical_json_bytes(value: &Value) -> Vec<u8> {
let mut output = Vec::new();
write_value(value, &mut output);
output
}
fn write_value(value: &Value, output: &mut Vec<u8>) {
match value {
Value::Null => output.extend_from_slice(b"null"),
Value::Bool(value) => output.extend_from_slice(if *value { b"true" } else { b"false" }),
Value::Number(value) => output.extend_from_slice(value.to_string().as_bytes()),
Value::String(value) => {
let encoded =
serde_json::to_string(value).expect("serializing a JSON string cannot fail");
output.extend_from_slice(encoded.as_bytes());
}
Value::Array(values) => {
output.push(b'[');
for (index, value) in values.iter().enumerate() {
if index > 0 {
output.push(b',');
}
write_value(value, output);
}
output.push(b']');
}
Value::Object(values) => {
output.push(b'{');
let mut keys: Vec<_> = values.keys().collect();
keys.sort_unstable();
for (index, key) in keys.iter().enumerate() {
if index > 0 {
output.push(b',');
}
let encoded =
serde_json::to_string(key).expect("serializing a JSON key cannot fail");
output.extend_from_slice(encoded.as_bytes());
output.push(b':');
write_value(&values[*key], output);
}
output.push(b'}');
}
}
}
pub fn hash_bytes(bytes: impl AsRef<[u8]>) -> String {
blake3::hash(bytes.as_ref()).to_hex().to_string()
}
pub fn hash_json(value: &Value) -> String {
hash_bytes(canonical_json_bytes(value))
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn object_key_order_does_not_change_hash() {
let left = json!({"z": 1, "a": {"two": 2, "one": 1}});
let right = json!({"a": {"one": 1, "two": 2}, "z": 1});
assert_eq!(canonical_json_bytes(&left), canonical_json_bytes(&right));
assert_eq!(hash_json(&left), hash_json(&right));
}
#[test]
fn array_order_changes_hash() {
assert_ne!(hash_json(&json!([1, 2])), hash_json(&json!([2, 1])));
}
}