use serde_json::Value;
#[must_use]
pub fn canon_args(args_json: &str) -> String {
serde_json::from_str::<Value>(args_json).map_or_else(
|_| args_json.trim().to_owned(),
|v| sort_json_keys(&v).to_string(),
)
}
fn sort_json_keys(v: &Value) -> Value {
match v {
Value::Object(map) => {
let mut entries: Vec<(&String, &Value)> = map.iter().collect();
entries.sort_unstable_by(|a, b| a.0.cmp(b.0));
Value::Object(
entries
.into_iter()
.map(|(k, val)| (k.clone(), sort_json_keys(val)))
.collect(),
)
}
Value::Array(arr) => Value::Array(arr.iter().map(sort_json_keys).collect()),
other => other.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canon_args_normalizes_key_order() {
assert_eq!(
canon_args(r#"{"template":"x","name":"y"}"#),
canon_args(r#"{"name":"y","template":"x"}"#)
);
assert_eq!(
canon_args(r#"{"b":1,"a":{"d":2,"c":3}}"#),
r#"{"a":{"c":3,"d":2},"b":1}"#
);
assert_ne!(canon_args(r#"{"name":"y"}"#), canon_args(r#"{"name":"z"}"#));
assert_eq!(canon_args(" not json "), "not json");
}
#[test]
fn canon_args_sorts_keys_inside_arrays() {
assert_eq!(
canon_args(r#"{"list":[{"z":1,"a":2}]}"#),
r#"{"list":[{"a":2,"z":1}]}"#
);
}
}