Skip to main content

edda_core/
canon.rs

1use serde_json::Value;
2
3/// Produce canonical JSON bytes: object keys sorted lexicographically (recursive),
4/// arrays preserve order, no extra whitespace.
5pub fn canonical_json_bytes(value: &Value) -> Result<Vec<u8>, serde_json::Error> {
6    let sorted = sort_value(value);
7    serde_json::to_vec(&sorted)
8}
9
10fn sort_value(value: &Value) -> Value {
11    match value {
12        Value::Object(map) => {
13            let mut pairs: Vec<(&String, Value)> =
14                map.iter().map(|(k, v)| (k, sort_value(v))).collect();
15            pairs.sort_by(|a, b| a.0.cmp(b.0));
16            let sorted_map: serde_json::Map<String, Value> =
17                pairs.into_iter().map(|(k, v)| (k.clone(), v)).collect();
18            Value::Object(sorted_map)
19        }
20        Value::Array(arr) => Value::Array(arr.iter().map(sort_value).collect()),
21        other => other.clone(),
22    }
23}
24
25#[cfg(test)]
26mod tests {
27    use super::*;
28
29    #[test]
30    fn keys_sorted_lexicographically() {
31        let input: Value = serde_json::from_str(r#"{"z":1,"a":2,"m":3}"#).unwrap();
32        let bytes = canonical_json_bytes(&input).unwrap();
33        let output = String::from_utf8(bytes).unwrap();
34        assert_eq!(output, r#"{"a":2,"m":3,"z":1}"#);
35    }
36
37    #[test]
38    fn nested_objects_sorted() {
39        let input: Value = serde_json::from_str(r#"{"b":{"z":1,"a":2},"a":1}"#).unwrap();
40        let bytes = canonical_json_bytes(&input).unwrap();
41        let output = String::from_utf8(bytes).unwrap();
42        assert_eq!(output, r#"{"a":1,"b":{"a":2,"z":1}}"#);
43    }
44
45    #[test]
46    fn arrays_preserve_order() {
47        let input: Value = serde_json::from_str(r#"{"a":[3,1,2]}"#).unwrap();
48        let bytes = canonical_json_bytes(&input).unwrap();
49        let output = String::from_utf8(bytes).unwrap();
50        assert_eq!(output, r#"{"a":[3,1,2]}"#);
51    }
52
53    #[test]
54    fn scalars_unchanged() {
55        let input: Value = serde_json::from_str(r#""hello""#).unwrap();
56        let bytes = canonical_json_bytes(&input).unwrap();
57        let output = String::from_utf8(bytes).unwrap();
58        assert_eq!(output, r#""hello""#);
59    }
60}