use serde::ser::{SerializeMap, SerializeSeq};
use serde::{Serialize, Serializer};
use serde_json::{Map, Value};
#[derive(Clone, Debug, PartialEq)]
pub struct CanonicalJson(Value);
impl CanonicalJson {
#[must_use]
pub const fn new(value: Value) -> Self {
Self(value)
}
#[must_use]
pub const fn as_value(&self) -> &Value {
&self.0
}
}
impl Serialize for CanonicalJson {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_value(&self.0, serializer)
}
}
#[must_use]
pub fn sorted_entries(object: &Map<String, Value>) -> Vec<(&String, &Value)> {
let mut sorted = object.iter().collect::<Vec<_>>();
sorted.sort_unstable_by_key(|(key, _)| *key);
sorted
}
pub fn serialize_value<S>(value: &Value, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Value::Null => serializer.serialize_unit(),
Value::Bool(value) => serializer.serialize_bool(*value),
Value::Number(number) => number.serialize(serializer),
Value::String(text) => serializer.serialize_str(text),
Value::Array(items) => {
let mut sequence = serializer.serialize_seq(Some(items.len()))?;
for item in items {
sequence.serialize_element(&Borrowed(item))?;
}
sequence.end()
}
Value::Object(entries) => {
let sorted = sorted_entries(entries);
let mut map = serializer.serialize_map(Some(sorted.len()))?;
for (key, value) in sorted {
map.serialize_entry(key, &Borrowed(value))?;
}
map.end()
}
}
}
struct Borrowed<'value>(&'value Value);
impl Serialize for Borrowed<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_value(self.0, serializer)
}
}
#[cfg(test)]
mod tests {
use serde::Serialize;
use serde_json::{Map, Value, json};
use super::{CanonicalJson, serialize_value, sorted_entries};
fn two_insertion_orders() -> (Map<String, Value>, Map<String, Value>) {
let mut forward = Map::new();
forward.insert("alpha".to_owned(), json!(1));
forward.insert("beta".to_owned(), json!(2));
forward.insert("gamma".to_owned(), json!(3));
let mut reversed = Map::new();
reversed.insert("gamma".to_owned(), json!(3));
reversed.insert("beta".to_owned(), json!(2));
reversed.insert("alpha".to_owned(), json!(1));
(forward, reversed)
}
#[test]
fn canonical_bytes_are_independent_of_insertion_order() -> Result<(), serde_json::Error> {
let (forward, reversed) = two_insertion_orders();
assert_eq!(
serde_json::to_string(&CanonicalJson::new(Value::Object(forward)))?,
serde_json::to_string(&CanonicalJson::new(Value::Object(reversed)))?
);
Ok(())
}
#[test]
fn canonical_bytes_are_key_sorted_at_every_depth() -> Result<(), serde_json::Error> {
let mut outer = Map::new();
outer.insert("zulu".to_owned(), json!({ "yankee": 1, "xray": 2 }));
outer.insert(
"alpha".to_owned(),
json!([{ "delta": 1, "charlie": 2 }, { "bravo": 3 }]),
);
let document = CanonicalJson::new(Value::Object(outer));
assert_eq!(
serde_json::to_string(&document)?,
r#"{"alpha":[{"charlie":2,"delta":1},{"bravo":3}],"zulu":{"xray":2,"yankee":1}}"#
);
Ok(())
}
#[test]
fn canonical_encoding_matches_a_key_sorted_expectation() -> Result<(), serde_json::Error> {
let (_, reversed) = two_insertion_orders();
assert_eq!(
serde_json::to_string_pretty(&CanonicalJson::new(Value::Object(reversed)))?,
"{\n \"alpha\": 1,\n \"beta\": 2,\n \"gamma\": 3\n}"
);
Ok(())
}
#[test]
fn scalars_arrays_and_nulls_round_trip_unchanged() -> Result<(), serde_json::Error> {
let value = json!({
"flag": true,
"count": -7,
"ratio": 1.5,
"absent": Value::Null,
"order": ["c", "a", "b"],
"text": "verbatim"
});
let encoded = serde_json::to_string(&CanonicalJson::new(value.clone()))?;
let decoded: Value = serde_json::from_str(&encoded)?;
assert_eq!(decoded, value, "canonical encoding must be lossless");
assert!(
encoded.contains(r#""order":["c","a","b"]"#),
"array order is content and must survive verbatim: {encoded}"
);
Ok(())
}
#[test]
fn serialize_value_canonicalises_a_field_of_a_derived_type() -> Result<(), serde_json::Error> {
#[derive(Serialize)]
struct Record {
name: String,
#[serde(serialize_with = "serialize_value")]
schema: Value,
}
let (forward, reversed) = two_insertion_orders();
let first = Record {
name: "probe".to_owned(),
schema: Value::Object(forward),
};
let second = Record {
name: "probe".to_owned(),
schema: Value::Object(reversed),
};
assert_eq!(
serde_json::to_string(&first)?,
r#"{"name":"probe","schema":{"alpha":1,"beta":2,"gamma":3}}"#
);
assert_eq!(
serde_json::to_string(&first)?,
serde_json::to_string(&second)?
);
Ok(())
}
#[test]
fn sorted_entries_is_key_order_whatever_the_insertion_order() {
let (forward, reversed) = two_insertion_orders();
let keys = |object| {
sorted_entries(object)
.into_iter()
.map(|(key, _)| key.clone())
.collect::<Vec<_>>()
};
assert_eq!(keys(&forward), vec!["alpha", "beta", "gamma"]);
assert_eq!(keys(&reversed), keys(&forward));
}
#[test]
fn as_value_exposes_the_wrapped_document() {
let document = CanonicalJson::new(json!({ "beta": 1, "alpha": 2 }));
assert_eq!(document.as_value()["alpha"], json!(2));
}
}