use serde::Serialize;
use serde_json::Value;
pub fn to_bytes<T: Serialize + ?Sized>(value: &T) -> Result<Vec<u8>, serde_json::Error> {
let value = serde_json::to_value(value)?;
Ok(value_bytes(&value))
}
#[must_use]
pub fn value_bytes(value: &Value) -> Vec<u8> {
let mut out = Vec::new();
write_canonical(value, &mut out);
out
}
fn utf16_order(a: &str, b: &str) -> std::cmp::Ordering {
a.encode_utf16().cmp(b.encode_utf16())
}
fn write_canonical(value: &Value, out: &mut Vec<u8>) {
match value {
Value::Object(map) => {
out.push(b'{');
let mut keys: Vec<&str> = map.keys().map(String::as_str).collect();
keys.sort_unstable_by(|a, b| utf16_order(a, b));
for (i, key) in keys.iter().enumerate() {
if i > 0 {
out.push(b',');
}
write_scalar(&Value::String((*key).to_owned()), out);
out.push(b':');
write_canonical(&map[*key], out);
}
out.push(b'}');
}
Value::Array(items) => {
out.push(b'[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push(b',');
}
write_canonical(item, out);
}
out.push(b']');
}
scalar => write_scalar(scalar, out),
}
}
fn write_scalar(value: &Value, out: &mut Vec<u8>) {
serde_json::to_writer(&mut *out, value).expect("serde_json cannot fail on a scalar Value");
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn sorted_keys_guard() {
let a = json!({"z": 1, "a": 2, "m": 3});
let b = json!({"a": 2, "m": 3, "z": 1});
assert_eq!(
value_bytes(&a),
value_bytes(&b),
"serde_json must sort object keys — is `preserve_order` enabled?"
);
assert_eq!(
String::from_utf8(value_bytes(&a)).unwrap(),
r#"{"a":2,"m":3,"z":1}"#
);
}
#[test]
fn keys_sort_by_utf16_code_unit_not_utf8_byte() {
let bmp = "\u{ffff}";
let astral = "\u{10000}";
assert!(
bmp < astral,
"the fixture no longer distinguishes the orderings"
);
let bytes = value_bytes(&json!({ astral: 1, bmp: 2 }));
let text = String::from_utf8(bytes).unwrap();
assert!(
text.find(astral) < text.find(bmp),
"keys came out in UTF-8 byte order, not UTF-16 — so a signed Agent \
Card canonicalized here is rejected by any conforming verifier, and \
every ASCII test still passes: {text}"
);
}
#[test]
fn nested_objects_are_also_sorted() {
let a = json!({"outer": {"z": 1, "a": 2}});
let b = json!({"outer": {"a": 2, "z": 1}});
assert_eq!(value_bytes(&a), value_bytes(&b));
}
#[test]
fn array_order_is_significant() {
assert_ne!(value_bytes(&json!([1, 2])), value_bytes(&json!([2, 1])));
}
}