Skip to main content

code_ranker_graph/
serialize.rs

1//! Canonical (deterministic) JSON serialization. Object keys come out
2//! alphabetically (`serde_json::Value` is backed by a `BTreeMap`) and the
3//! `nodes`/`edges` arrays are sorted by a stable key, so unchanged input is
4//! byte-identical across runs. Generic over any `Serialize` — depends on
5//! nothing else in the crate.
6
7use serde::Serialize;
8
9/// Serialize to canonical pretty JSON: object keys come out alphabetically
10/// (`serde_json::Value` is backed by a `BTreeMap`), and the `nodes`/`edges`
11/// arrays are sorted by a stable key so unchanged input is byte-identical.
12pub fn to_canonical_string_pretty<T: Serialize>(value: &T) -> serde_json::Result<String> {
13    let mut v = serde_json::to_value(value)?;
14    canonicalize_value(&mut v);
15    serde_json::to_string_pretty(&v)
16}
17
18/// Compact counterpart of [`to_canonical_string_pretty`].
19pub fn to_canonical_string<T: Serialize>(value: &T) -> serde_json::Result<String> {
20    let mut v = serde_json::to_value(value)?;
21    canonicalize_value(&mut v);
22    serde_json::to_string(&v)
23}
24
25fn canonicalize_value(v: &mut serde_json::Value) {
26    match v {
27        serde_json::Value::Array(arr) => {
28            for item in arr.iter_mut() {
29                canonicalize_value(item);
30            }
31        }
32        serde_json::Value::Object(map) => {
33            for val in map.values_mut() {
34                canonicalize_value(val);
35            }
36            if let Some(serde_json::Value::Array(nodes)) = map.get_mut("nodes") {
37                nodes.sort_by_key(|a| json_str(a, "id"));
38            }
39            if let Some(serde_json::Value::Array(edges)) = map.get_mut("edges") {
40                edges.sort_by(|a, b| {
41                    json_str(a, "source")
42                        .cmp(&json_str(b, "source"))
43                        .then_with(|| json_str(a, "target").cmp(&json_str(b, "target")))
44                        .then_with(|| json_str(a, "kind").cmp(&json_str(b, "kind")))
45                });
46            }
47        }
48        _ => {}
49    }
50}
51
52fn json_str(v: &serde_json::Value, key: &str) -> String {
53    v.get(key)
54        .and_then(|x| x.as_str())
55        .unwrap_or_default()
56        .to_string()
57}