Skip to main content

jzon/
fixed.rs

1//! Zero-allocation, stack-based JSON output via const-generic fixed buffers.
2
3/// Stack-allocated, const-generic byte buffer for zero-allocation JSON output.
4/// `N` is the maximum bytes; exceeding it panics.
5pub struct FixedBuf<const N: usize> {
6    data: [u8; N],
7    len: usize,
8}
9
10impl<const N: usize> Default for FixedBuf<N> {
11    fn default() -> Self { Self::new() }
12}
13
14impl<const N: usize> FixedBuf<N> {
15    /// Construct an empty `FixedBuf` (const-fn, lives on the stack).
16    pub const fn new() -> Self {
17        FixedBuf { data: [0u8; N], len: 0 }
18    }
19
20    #[inline] pub fn as_slice(&self) -> &[u8] { &self.data[..self.len] }
21
22    /// # Panics
23    /// If content is not valid UTF-8 (never happens for correct `ToJson` impls).
24    #[inline]
25    pub fn as_str(&self) -> &str {
26        core::str::from_utf8(self.as_slice()).expect("ToJson always emits valid UTF-8")
27    }
28
29    #[inline] pub fn len(&self) -> usize { self.len }
30    #[inline] pub fn is_empty(&self) -> bool { self.len == 0 }
31    #[inline] pub fn remaining(&self) -> usize { N - self.len }
32    #[inline] pub fn clear(&mut self) { self.len = 0; }
33}
34
35impl<const N: usize> FixedBuf<N> {
36    #[inline(always)]
37    fn write_bytes(&mut self, bs: &[u8]) {
38        let end = self.len + bs.len();
39        self.data[self.len..end].copy_from_slice(bs);
40        self.len = end;
41    }
42}
43
44impl<const N: usize> core::fmt::Debug for FixedBuf<N> {
45    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46        f.debug_tuple("FixedBuf").field(&self.as_str()).finish()
47    }
48}
49
50use crate::ser::ToJson;
51
52impl<T: ToJson + ?Sized> ToJsonExt for T {}
53
54/// Extension trait adding stack-buffer and reuse helpers for every `T: ToJson`.
55pub trait ToJsonExt: ToJson {
56    /// Serialize into a stack-allocated [`FixedBuf<N>`]. Returns `None` if output exceeds `N`.
57    #[inline]
58    fn to_fixed_buf<const N: usize>(&self) -> Option<FixedBuf<N>> {
59        let mut tmp = Vec::with_capacity(N);
60        self.json_write(&mut tmp);
61        if tmp.len() > N { return None; }
62        let mut buf = FixedBuf::<N>::new();
63        buf.write_bytes(&tmp);
64        Some(buf)
65    }
66
67    /// Serialize to a pre-allocated `Vec<u8>`, clearing it first (amortizes allocation).
68    #[inline]
69    fn json_write_reuse<'a>(&self, buf: &'a mut Vec<u8>) -> &'a [u8] {
70        buf.clear();
71        self.json_write(buf);
72        buf.as_slice()
73    }
74
75    /// Serialize to any `io::Write`.
76    fn json_write_io(&self, mut w: impl std::io::Write) -> std::io::Result<()> {
77        w.write_all(&self.to_json_bytes())
78    }
79}
80
81/// Compute the exact serialized length of a JSON string (quotes + escapes) at compile time.
82pub const fn json_str_len(s: &[u8]) -> usize {
83    let mut len = 2; // surrounding quotes
84    let mut i = 0;
85    while i < s.len() {
86        len += match s[i] {
87            b'"' | b'\\' | b'\n' | b'\r' | b'\t' | 0x08 | 0x0C => 2,
88            0x00..=0x1F => 6, // \uXXXX
89            _ => 1,
90        };
91        i += 1;
92    }
93    len
94}