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