Skip to main content

jzon/
ser.rs

1//! `ToJson` trait and primitive implementations.
2
3use std::collections::{BTreeMap, HashMap};
4
5pub trait ToJson {
6    fn json_write(&self, w: &mut Vec<u8>);
7
8    /// Hint for the approximate number of bytes this value will serialize to.
9    ///
10    /// This is used by `to_json_bytes` to pre-allocate the output buffer,
11    /// avoiding reallocations for the common case.  Implementations should
12    /// return a value that is *at least* as large as the serialized form in
13    /// the common case — over-estimating is fine, under-estimating causes a
14    /// single reallocation.  The default (64) is conservative.
15    #[inline]
16    fn json_size_hint(&self) -> usize { 64 }
17
18    #[must_use]
19    fn to_json_bytes(&self) -> Vec<u8> {
20        let mut buf = Vec::with_capacity(self.json_size_hint());
21        self.json_write(&mut buf);
22        buf
23    }
24
25    #[must_use]
26    fn to_json_string(&self) -> String {
27        // SAFETY: ToJson implementations only write valid UTF-8 bytes.
28        // The expect path is unreachable for any correct impl; using expect (not
29        // unwrap_unchecked) to preserve a clear panic message for buggy custom impls.
30        String::from_utf8(self.to_json_bytes())
31            .expect("ToJson implementations always emit valid UTF-8")
32    }
33}
34
35// ── string escaping ───────────────────────────────────────────────────────────
36//
37// `write_escaped_str` delegates byte scanning to `crate::simd::find_escape`,
38// which dispatches to the widest available implementation:
39//   - nightly + simd feature  → 32-byte std::simd lanes  (find_escape_simd32)
40//   - stable + simd feature   → 16-byte u128 SWAR        (find_escape_simd16)
41//   - no simd feature         → scalar byte-by-byte      (find_escape_scalar)
42//
43// This keeps ser.rs free of SWAR arithmetic — all the bit tricks live in simd.rs.
44
45#[inline]
46pub fn write_escaped_str(s: &str, w: &mut Vec<u8>) {
47    w.push(b'"');
48    // Pre-reserve: common case is no escaping, so reserve s.len() + 1 (closing quote).
49    // Avoids all reallocations in the fast (no-escape) path.
50    w.reserve(s.len() + 1);
51    let bytes = s.as_bytes();
52    let mut start = 0usize; // start of current unescaped run
53
54    let mut i = start;
55    while i < bytes.len() {
56        // Find the next byte that needs escaping using the widest available path.
57        let stop = crate::simd::find_escape(bytes, i);
58        if stop >= bytes.len() {
59            // No more bytes need escaping; flush the rest in one go.
60            break;
61        }
62        // Flush safe bytes [start..stop], then emit the escape sequence.
63        w.extend_from_slice(&bytes[start..stop]);
64        escape_one(bytes[stop], w);
65        i = stop + 1;
66        start = i;
67    }
68
69    // Flush the final safe run.
70    w.extend_from_slice(&bytes[start..]);
71    w.push(b'"');
72}
73
74#[inline(always)]
75fn escape_one(b: u8, w: &mut Vec<u8>) {
76    match b {
77        b'"'  => w.extend_from_slice(b"\\\""),
78        b'\\' => w.extend_from_slice(b"\\\\"),
79        b'\n' => w.extend_from_slice(b"\\n"),
80        b'\r' => w.extend_from_slice(b"\\r"),
81        b'\t' => w.extend_from_slice(b"\\t"),
82        0x08  => w.extend_from_slice(b"\\b"),
83        0x0C  => w.extend_from_slice(b"\\f"),
84        b     => {
85            // Other control characters as \u00XX
86            let hi = b >> 4;
87            let lo = b & 0xF;
88            w.extend_from_slice(&[
89                b'\\', b'u', b'0', b'0',
90                if hi < 10 { b'0' + hi } else { b'a' + hi - 10 },
91                if lo < 10 { b'0' + lo } else { b'a' + lo - 10 },
92            ]);
93        }
94    }
95}
96
97// ── primitive impls ───────────────────────────────────────────────────────────
98
99impl ToJson for bool {
100    #[inline]
101    fn json_write(&self, w: &mut Vec<u8>) {
102        w.extend_from_slice(if *self { b"true" } else { b"false" });
103    }
104    #[inline] fn json_size_hint(&self) -> usize { 5 } // "false"
105}
106
107impl ToJson for str {
108    #[inline]
109    fn json_write(&self, w: &mut Vec<u8>) { write_escaped_str(self, w); }
110    #[inline] fn json_size_hint(&self) -> usize { self.len() + 2 }
111}
112
113impl ToJson for String {
114    #[inline]
115    fn json_write(&self, w: &mut Vec<u8>) { write_escaped_str(self, w); }
116    #[inline] fn json_size_hint(&self) -> usize { self.len() + 2 }
117}
118
119impl ToJson for crate::scanner::JsonStr<'_> {
120    #[inline]
121    fn json_write(&self, w: &mut Vec<u8>) {
122        use crate::scanner::JsonStr;
123        match self {
124            JsonStr::BorrowedNoEsc(s) => {
125                // Provably escape-free — skip find_escape scan.
126                w.reserve(s.len() + 2);
127                w.push(b'"');
128                w.extend_from_slice(s.as_bytes());
129                w.push(b'"');
130            }
131            JsonStr::Borrowed(s) => write_escaped_str(s, w),
132            JsonStr::Owned(s)    => write_escaped_str(s, w),
133        }
134    }
135    #[inline]
136    fn json_size_hint(&self) -> usize { self.as_str().len() + 2 }
137}
138
139impl<T: ToJson + ?Sized> ToJson for &T {
140    #[inline]
141    fn json_write(&self, w: &mut Vec<u8>) { (**self).json_write(w); }
142    #[inline] fn json_size_hint(&self) -> usize { (**self).json_size_hint() }
143}
144
145impl<T: ToJson> ToJson for Box<T> {
146    #[inline]
147    fn json_write(&self, w: &mut Vec<u8>) { (**self).json_write(w); }
148    #[inline] fn json_size_hint(&self) -> usize { (**self).json_size_hint() }
149}
150
151impl<T: ToJson> ToJson for Option<T> {
152    #[inline]
153    fn json_write(&self, w: &mut Vec<u8>) {
154        match self {
155            Some(v) => v.json_write(w),
156            None    => w.extend_from_slice(b"null"),
157        }
158    }
159    #[inline]
160    fn json_size_hint(&self) -> usize {
161        match self {
162            Some(v) => v.json_size_hint(),
163            None    => 4, // "null"
164        }
165    }
166}
167
168impl<T: ToJson> ToJson for Vec<T> {
169    fn json_write(&self, w: &mut Vec<u8>) {
170        w.push(b'[');
171        let mut first = true;
172        for item in self {
173            if !first { w.push(b','); }
174            item.json_write(w);
175            first = false;
176        }
177        w.push(b']');
178    }
179    #[inline]
180    fn json_size_hint(&self) -> usize {
181        if self.is_empty() { return 2; }
182        // Use the first element's hint as a sample; add separating commas.
183        2 + self.len() * (self[0].json_size_hint() + 1)
184    }
185}
186
187impl<T: ToJson, const N: usize> ToJson for [T; N] {
188    fn json_write(&self, w: &mut Vec<u8>) {
189        w.push(b'[');
190        let mut first = true;
191        for item in self {
192            if !first { w.push(b','); }
193            item.json_write(w);
194            first = false;
195        }
196        w.push(b']');
197    }
198    #[inline]
199    fn json_size_hint(&self) -> usize {
200        if N == 0 { return 2; }
201        2 + N * (self[0].json_size_hint() + 1)
202    }
203}
204
205impl<T: ToJson> ToJson for [T] {
206    fn json_write(&self, w: &mut Vec<u8>) {
207        w.push(b'[');
208        let mut first = true;
209        for item in self {
210            if !first { w.push(b','); }
211            item.json_write(w);
212            first = false;
213        }
214        w.push(b']');
215    }
216    #[inline]
217    fn json_size_hint(&self) -> usize {
218        if self.is_empty() { return 2; }
219        2 + self.len() * (self[0].json_size_hint() + 1)
220    }
221}
222
223// Note: A specialized impl for Vec<f64> is not possible on stable Rust due to
224// the coherence rules (conflicts with impl<T: ToJson> ToJson for Vec<T>).
225// The generic Vec<T> impl with json_size_hint delegating to f64::json_size_hint (10)
226// covers the f64 case correctly through monomorphization.
227
228// ── integer writers (no format! overhead) ─────────────────────────────────────
229
230#[inline(always)]
231pub fn write_u64(mut n: u64, w: &mut Vec<u8>) {
232    if n == 0 { w.push(b'0'); return; }
233    let mut tmp = [0u8; 20];
234    let mut len = 0usize;
235    while n > 0 { tmp[len] = b'0' + (n % 10) as u8; n /= 10; len += 1; }
236    tmp[..len].reverse();
237    w.extend_from_slice(&tmp[..len]);
238}
239
240#[inline(always)]
241pub fn write_i64(n: i64, w: &mut Vec<u8>) {
242    if n < 0 { w.push(b'-'); write_u64(n.unsigned_abs(), w); } else { write_u64(n as u64, w); }
243}
244
245macro_rules! impl_uint {
246    ($($t:ty, $hint:expr),*) => {$(
247        impl ToJson for $t {
248            #[inline] fn json_write(&self, w: &mut Vec<u8>) { write_u64(*self as u64, w); }
249            #[inline] fn json_size_hint(&self) -> usize { $hint }
250        }
251    )*};
252}
253macro_rules! impl_sint {
254    ($($t:ty, $hint:expr),*) => {$(
255        impl ToJson for $t {
256            #[inline] fn json_write(&self, w: &mut Vec<u8>) { write_i64(*self as i64, w); }
257            #[inline] fn json_size_hint(&self) -> usize { $hint }
258        }
259    )*};
260}
261// Tight upper bounds (max digit count including sign for signed types):
262//   u8:3, u16:5, u32:10, u64:20, u128:39, usize:20
263//   i8:4, i16:6, i32:11, i64:20, i128:40, isize:20
264impl_uint!(u8, 3, u16, 5, u32, 10, u64, 20, usize, 20);
265impl_sint!(i8, 4, i16, 6, i32, 11, i64, 20, isize, 20);
266
267// u128 / i128: cannot pass through u64/i64, need dedicated digit writers.
268#[inline]
269fn write_u128(mut n: u128, w: &mut Vec<u8>) {
270    if n == 0 { w.push(b'0'); return; }
271    let mut tmp = [0u8; 39];
272    let mut len = 0usize;
273    while n > 0 { tmp[len] = b'0' + (n % 10) as u8; n /= 10; len += 1; }
274    tmp[..len].reverse();
275    w.extend_from_slice(&tmp[..len]);
276}
277impl ToJson for u128 {
278    #[inline] fn json_write(&self, w: &mut Vec<u8>) { write_u128(*self, w); }
279    #[inline] fn json_size_hint(&self) -> usize { 39 }
280}
281impl ToJson for i128 {
282    #[inline]
283    fn json_write(&self, w: &mut Vec<u8>) {
284        if *self < 0 { w.push(b'-'); write_u128(self.unsigned_abs(), w); } else { write_u128(*self as u128, w); }
285    }
286    #[inline] fn json_size_hint(&self) -> usize { 40 }
287}
288
289impl ToJson for f64 {
290    #[inline]
291    fn json_write(&self, w: &mut Vec<u8>) {
292        if !self.is_finite() { w.extend_from_slice(b"null"); return; }
293        // ECMA-404 / ECMA-262 §24.5.2.4: -0 must serialise as "0".
294        // IEEE 754: -0.0 == 0.0, so this check catches both.
295        if *self == 0.0 { w.extend_from_slice(b"0"); return; }
296        #[cfg(feature = "zmij-float-ser")]
297        {
298            let mut buf = zmij::Buffer::new();
299            w.extend_from_slice(buf.format_finite(*self).as_bytes());
300            return;
301        }
302        #[cfg(all(feature = "fast-float", not(feature = "zmij-float-ser")))]
303        {
304            let mut buf = ryu::Buffer::new();
305            w.extend_from_slice(buf.format_finite(*self).as_bytes());
306            return;
307        }
308        #[cfg(not(any(feature = "fast-float", feature = "zmij-float-ser")))]
309        w.extend_from_slice(format!("{}", self).as_bytes());
310    }
311    /// ryu's worst-case output for f64 is 24 characters, but the practical output for
312    /// typical floats (integers, short decimals, small exponents) is 2–6 characters.
313    /// Using 10 as the hint covers the vast majority of real-world floats without the
314    /// 24-byte worst-case causing 96-byte allocations for small structs.  Under-estimation
315    /// only causes a single reallocation, whereas over-estimation wastes allocator headroom
316    /// and pushes small structs into larger (slower) allocator size classes.
317    #[inline] fn json_size_hint(&self) -> usize { 10 }
318}
319
320impl ToJson for f32 {
321    #[inline]
322    fn json_write(&self, w: &mut Vec<u8>) {
323        if !self.is_finite() { w.extend_from_slice(b"null"); return; }
324        #[cfg(feature = "zmij-float-ser")]
325        {
326            let mut buf = zmij::Buffer::new();
327            w.extend_from_slice(buf.format_finite(*self).as_bytes());
328            return;
329        }
330        #[cfg(all(feature = "fast-float", not(feature = "zmij-float-ser")))]
331        {
332            let mut buf = ryu::Buffer::new();
333            w.extend_from_slice(buf.format_finite(*self).as_bytes());
334            return;
335        }
336        #[cfg(not(any(feature = "fast-float", feature = "zmij-float-ser")))]
337        w.extend_from_slice(format!("{}", self).as_bytes());
338    }
339    /// ryu's output for f32 is at most 14 characters.
340    #[inline] fn json_size_hint(&self) -> usize { 14 }
341}
342
343// ── char ──────────────────────────────────────────────────────────────────────
344
345impl ToJson for char {
346    #[inline]
347    fn json_write(&self, w: &mut Vec<u8>) {
348        let mut buf = [0u8; 4];
349        write_escaped_str(self.encode_utf8(&mut buf), w);
350    }
351    /// At most 4 UTF-8 bytes + 2 surrounding quotes.
352    #[inline] fn json_size_hint(&self) -> usize { 6 }
353}
354
355// ── unit → null ───────────────────────────────────────────────────────────────
356
357impl ToJson for () {
358    #[inline] fn json_write(&self, w: &mut Vec<u8>) { w.extend_from_slice(b"null"); }
359    #[inline] fn json_size_hint(&self) -> usize { 4 }
360}
361
362// ── HashMap / BTreeMap → JSON objects ────────────────────────────────────────
363
364impl<K: ToJson, V: ToJson> ToJson for HashMap<K, V> {
365    fn json_write(&self, w: &mut Vec<u8>) {
366        w.push(b'{');
367        let mut first = true;
368        for (k, v) in self {
369            if !first { w.push(b','); }
370            first = false;
371            k.json_write(w);
372            w.push(b':');
373            v.json_write(w);
374        }
375        w.push(b'}');
376    }
377    #[inline]
378    fn json_size_hint(&self) -> usize {
379        if self.is_empty() { return 2; }
380        let (k, v) = self.iter().next().unwrap();
381        2 + self.len() * (k.json_size_hint() + 1 + v.json_size_hint() + 1)
382    }
383}
384
385impl<K: ToJson, V: ToJson> ToJson for BTreeMap<K, V> {
386    fn json_write(&self, w: &mut Vec<u8>) {
387        w.push(b'{');
388        let mut first = true;
389        for (k, v) in self {
390            if !first { w.push(b','); }
391            first = false;
392            k.json_write(w);
393            w.push(b':');
394            v.json_write(w);
395        }
396        w.push(b'}');
397    }
398    #[inline]
399    fn json_size_hint(&self) -> usize {
400        if self.is_empty() { return 2; }
401        let (k, v) = self.iter().next().unwrap();
402        2 + self.len() * (k.json_size_hint() + 1 + v.json_size_hint() + 1)
403    }
404}
405
406// ── tuples → JSON arrays (1- to 12-element) ───────────────────────────────────
407
408macro_rules! impl_tuple_to_json {
409    ($($T:ident . $idx:tt),+) => {
410        impl<$($T: ToJson),+> ToJson for ($($T,)+) {
411            fn json_write(&self, w: &mut Vec<u8>) {
412                w.push(b'[');
413                let mut first = true;
414                $( if !first { w.push(b','); } first = false; self.$idx.json_write(w); )+
415                let _ = first;
416                w.push(b']');
417            }
418            #[inline]
419            fn json_size_hint(&self) -> usize {
420                2 + $( self.$idx.json_size_hint() + 1 + )+ 0
421                  - 1 // subtract trailing extra comma count
422            }
423        }
424    };
425}
426
427impl_tuple_to_json!(A.0);
428impl_tuple_to_json!(A.0, B.1);
429impl_tuple_to_json!(A.0, B.1, C.2);
430impl_tuple_to_json!(A.0, B.1, C.2, D.3);
431impl_tuple_to_json!(A.0, B.1, C.2, D.3, E.4);
432impl_tuple_to_json!(A.0, B.1, C.2, D.3, E.4, F.5);
433impl_tuple_to_json!(A.0, B.1, C.2, D.3, E.4, F.5, G.6);
434impl_tuple_to_json!(A.0, B.1, C.2, D.3, E.4, F.5, G.6, H.7);
435impl_tuple_to_json!(A.0, B.1, C.2, D.3, E.4, F.5, G.6, H.7, I.8);
436impl_tuple_to_json!(A.0, B.1, C.2, D.3, E.4, F.5, G.6, H.7, I.8, J.9);
437impl_tuple_to_json!(A.0, B.1, C.2, D.3, E.4, F.5, G.6, H.7, I.8, J.9, K.10);
438impl_tuple_to_json!(A.0, B.1, C.2, D.3, E.4, F.5, G.6, H.7, I.8, J.9, K.10, L.11);