use serde_json::Value;
pub fn to_canonical_string(value: &Value) -> Result<String, String> {
let mut out = String::new();
write_value(value, &mut out)?;
Ok(out)
}
fn write_value(value: &Value, out: &mut String) -> Result<(), String> {
match value {
Value::Null => out.push_str("null"),
Value::Bool(true) => out.push_str("true"),
Value::Bool(false) => out.push_str("false"),
Value::Number(n) => write_number(n, out)?,
Value::String(s) => write_string(s, out),
Value::Array(items) => {
out.push('[');
for (i, item) in items.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_value(item, out)?;
}
out.push(']');
}
Value::Object(map) => {
let mut keys: Vec<&String> = map.keys().collect();
keys.sort_by(|a, b| cmp_utf16(a, b));
out.push('{');
for (i, key) in keys.iter().enumerate() {
if i > 0 {
out.push(',');
}
write_string(key, out);
out.push(':');
write_value(&map[*key], out)?;
}
out.push('}');
}
}
Ok(())
}
fn write_number(n: &serde_json::Number, out: &mut String) -> Result<(), String> {
let f = n
.as_f64()
.ok_or_else(|| format!("number {n} is not representable as f64"))?;
if !f.is_finite() {
return Err("non-finite numbers are forbidden by JCS (schema spec §8.4)".to_string());
}
let f = if f == 0.0 { 0.0 } else { f }; let mut buf = ryu_js::Buffer::new();
out.push_str(buf.format(f));
Ok(())
}
fn write_string(s: &str, out: &mut String) {
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\u{08}' => out.push_str("\\b"),
'\u{09}' => out.push_str("\\t"),
'\u{0A}' => out.push_str("\\n"),
'\u{0C}' => out.push_str("\\f"),
'\u{0D}' => out.push_str("\\r"),
c if (c as u32) < 0x20 => {
out.push_str(&format!("\\u{:04x}", c as u32));
}
c => out.push(c),
}
}
out.push('"');
}
fn cmp_utf16(a: &str, b: &str) -> std::cmp::Ordering {
a.encode_utf16().cmp(b.encode_utf16())
}