#[cfg(feature = "alloc")]
use alloc::{
format,
string::{String, ToString},
};
use serde_json::Value;
pub(crate) fn to_canonical_string<T>(value: &T) -> serde_json::Result<String>
where
T: serde::Serialize,
{
let tree = serde_json::to_value(value)?;
let mut out = String::new();
write_value(&tree, 0, &mut out);
Ok(out)
}
fn push_indent(out: &mut String, depth: usize) {
for _ in 0..depth {
out.push_str(" ");
}
}
fn write_value(value: &Value, depth: usize, out: &mut String) {
match value {
Value::Null => out.push_str("null"),
Value::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
Value::Number(n) => out.push_str(&n.to_string()),
Value::String(s) => write_json_string(s, out),
Value::Array(items) => write_array(items, depth, out),
Value::Object(map) => write_object(map, depth, out),
}
}
fn write_array(items: &[Value], depth: usize, out: &mut String) {
if items.is_empty() {
out.push_str("[]");
return;
}
out.push_str("[\n");
let inner = depth + 1;
let last = items.len() - 1;
for (i, item) in items.iter().enumerate() {
push_indent(out, inner);
write_value(item, inner, out);
if i != last {
out.push(',');
}
out.push('\n');
}
push_indent(out, depth);
out.push(']');
}
fn write_object(map: &serde_json::Map<String, Value>, depth: usize, out: &mut String) {
if map.is_empty() {
out.push_str("{}");
return;
}
out.push_str("{\n");
let inner = depth + 1;
let last = map.len() - 1;
for (i, (key, val)) in map.iter().enumerate() {
push_indent(out, inner);
write_json_string(key, out);
out.push_str(": ");
write_value(val, inner, out);
if i != last {
out.push(',');
}
out.push('\n');
}
push_indent(out, depth);
out.push('}');
}
fn write_json_string(s: &str, out: &mut String) {
out.push('"');
for ch in s.chars() {
match ch {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\u{08}' => out.push_str("\\b"),
'\u{0c}' => out.push_str("\\f"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
c if c.is_ascii() => out.push(c),
c => {
let mut units = [0u16; 2];
for unit in c.encode_utf16(&mut units) {
out.push_str(&format!("\\u{:04x}", unit));
}
}
}
}
out.push('"');
}