conjure_cp_cli/utils/
json.rs

1use serde_json::Value;
2
3/// Compare two JSON values.
4/// If the values are String, Number, or Bool, they are compared directly.
5/// If the values are arrays, they are compared element-wise.
6/// Otherwise, they are compared as strings.
7fn json_value_cmp(a: &Value, b: &Value) -> std::cmp::Ordering {
8    match (a, b) {
9        (Value::Null, Value::Null) => std::cmp::Ordering::Equal,
10        (Value::Bool(a), Value::Bool(b)) => a.cmp(b),
11        (Value::String(a), Value::String(b)) => a.cmp(b),
12        (Value::Number(a), Value::Number(b)) => {
13            let af = a.as_f64().unwrap_or_default();
14            let bf = b.as_f64().unwrap_or_default();
15            af.total_cmp(&bf)
16        }
17        (Value::Array(a), Value::Array(b)) => {
18            for (a, b) in a.iter().zip(b.iter()) {
19                let cmp = json_value_cmp(a, b);
20                if cmp != std::cmp::Ordering::Equal {
21                    return cmp;
22                }
23            }
24            std::cmp::Ordering::Equal
25        }
26        _ => a.to_string().cmp(&b.to_string()),
27    }
28}
29
30/// Sort the "variables" field by name.
31/// We have to do this separately because that field is not a JSON object, instead it's an array of tuples.
32pub fn sort_json_variables(value: &Value) -> Value {
33    match value {
34        Value::Array(vars) => {
35            let mut vars_sorted = vars.clone();
36            vars_sorted.sort_by(json_value_cmp);
37            Value::Array(vars_sorted)
38        }
39        _ => value.clone(),
40    }
41}
42
43/// Recursively sorts the keys of all JSON objects within the provided JSON value.
44///
45/// serde_json will output JSON objects in an arbitrary key order.
46/// this is normally fine, except in our use case we wouldn't want to update the expected output again and again.
47/// so a consistent (sorted) ordering of the keys is desirable.
48///
49/// We do not sort contained Array objects since order does have meaning for things like matrix literals. We
50/// sort the outer-most array so we can meaningfully compare with Conjure's arbitrarily-ordered solutions.
51pub fn sort_json_object(value: &Value, sort_arrays: bool) -> Value {
52    match value {
53        Value::Object(obj) => {
54            let mut ordered: Vec<(String, Value)> = obj
55                .iter()
56                .map(|(k, v)| {
57                    if k == "variables" {
58                        (k.clone(), sort_json_variables(v))
59                    } else {
60                        (k.clone(), sort_json_object(v, false))
61                    }
62                })
63                .collect();
64
65            ordered.sort_by(|a, b| a.0.cmp(&b.0));
66            Value::Object(ordered.into_iter().collect())
67        }
68        Value::Array(arr) => {
69            let mut arr: Vec<Value> = arr.iter().map(|val| sort_json_object(val, false)).collect();
70
71            if sort_arrays {
72                arr.sort_by(json_value_cmp);
73            }
74
75            Value::Array(arr)
76        }
77        _ => value.clone(),
78    }
79}