Skip to main content

reliakit_json/
write.rs

1//! Compact, deterministic serialization.
2
3use alloc::string::String;
4use alloc::vec::Vec;
5
6use crate::value::JsonValue;
7
8/// Serializes a value to a compact JSON string (no insignificant whitespace).
9///
10/// Output is deterministic for a given value: object members are emitted in
11/// stored (insertion) order and numbers keep their exact representation. This
12/// is *not* the canonical (RFC 8785) form — it does not sort keys or reformat
13/// numbers. In-memory serialization cannot fail, so this is infallible.
14pub fn to_compact_string(value: &JsonValue) -> String {
15    let mut out = String::new();
16    write_value(&mut out, value);
17    out
18}
19
20/// Serializes a value to compact JSON bytes. See [`to_compact_string`].
21pub fn to_compact_vec(value: &JsonValue) -> Vec<u8> {
22    to_compact_string(value).into_bytes()
23}
24
25fn write_value(out: &mut String, value: &JsonValue) {
26    match value {
27        JsonValue::Null => out.push_str("null"),
28        JsonValue::Bool(true) => out.push_str("true"),
29        JsonValue::Bool(false) => out.push_str("false"),
30        JsonValue::Number(number) => out.push_str(number.as_str()),
31        JsonValue::String(string) => write_escaped(out, string),
32        JsonValue::Array(items) => {
33            out.push('[');
34            for (index, item) in items.iter().enumerate() {
35                if index > 0 {
36                    out.push(',');
37                }
38                write_value(out, item);
39            }
40            out.push(']');
41        }
42        JsonValue::Object(object) => {
43            out.push('{');
44            for (index, member) in object.iter().enumerate() {
45                if index > 0 {
46                    out.push(',');
47                }
48                write_escaped(out, member.key());
49                out.push(':');
50                write_value(out, member.value());
51            }
52            out.push('}');
53        }
54    }
55}
56
57pub(crate) fn write_escaped(out: &mut String, s: &str) {
58    out.push('"');
59    for c in s.chars() {
60        match c {
61            '"' => out.push_str("\\\""),
62            '\\' => out.push_str("\\\\"),
63            '\u{08}' => out.push_str("\\b"),
64            '\u{0C}' => out.push_str("\\f"),
65            '\n' => out.push_str("\\n"),
66            '\r' => out.push_str("\\r"),
67            '\t' => out.push_str("\\t"),
68            c if (c as u32) < 0x20 => {
69                out.push_str("\\u");
70                let code = c as u32;
71                for shift in [12u32, 8, 4, 0] {
72                    let nibble = ((code >> shift) & 0xF) as u8;
73                    let hex = if nibble < 10 {
74                        b'0' + nibble
75                    } else {
76                        b'a' + nibble - 10
77                    };
78                    out.push(hex as char);
79                }
80            }
81            c => out.push(c),
82        }
83    }
84    out.push('"');
85}