json_bourne/ser.rs
1//! Type-driven serialization.
2//!
3//! Mirror image of [`crate::de`]. Each type knows how to write itself to a
4//! [`JsonWrite`] sink; the top-level entry point [`to_string`] (and
5//! [`to_vec`]) drives a `StringSink` and returns the resulting buffer.
6//!
7//! The sink is not [`core::fmt::Write`] for two reasons: (1) `fmt::Write`
8//! returns an opaque `fmt::Error` and forces every numeric write through
9//! the `Formatter` machinery, which is ~3× slower than direct push for
10//! integers; (2) we need a typed error path for non-finite floats
11//! ([`ErrorKind::NonFiniteFloat`]). The sink trait exposes per-primitive
12//! writers so impls can bypass formatting entirely.
13//!
14//! Float impls are not included here — they live in a follow-up that ports
15//! the ryu shortest-round-trip algorithm. Non-float primitives, composites,
16//! and the std/alloc adapters are all in scope.
17
18use crate::{Error, ErrorKind, Position};
19
20#[cfg(feature = "alloc")]
21extern crate alloc;
22
23#[cfg(feature = "alloc")]
24use alloc::string::String;
25
26// ---------------------------------------------------------------------------
27// Sink trait + StringSink
28// ---------------------------------------------------------------------------
29
30/// Output sink for [`ToJson`] impls.
31///
32/// Implementors expose per-primitive writers so the trait impls below can
33/// avoid the [`core::fmt`] machinery. The default integer/float methods
34/// land on `write_str_raw` after formatting into a small stack buffer, but
35/// implementations that can write directly (e.g. `StringSink`'s integer
36/// LUT) override them.
37pub trait JsonWrite {
38 /// Sink-specific error type. `StringSink` uses [`core::convert::Infallible`]
39 /// for the byte/string writes; the typed-level [`to_string`] entry point
40 /// widens to [`crate::Error`] so non-finite floats can surface.
41 type Error;
42
43 /// Hint that at least `additional` more bytes will be written. Sinks
44 /// backed by a growable buffer (like [`ByteSink`]) can amortize
45 /// capacity growth across a known-size sequence; sinks without a
46 /// reservation concept treat this as a no-op.
47 ///
48 /// Hot path: the array writer in this module calls this once at the
49 /// start of a slice/Vec serialization so per-element `reserve` calls
50 /// become predictable no-ops.
51 #[inline]
52 fn reserve_hint(&mut self, _additional: usize) {}
53
54 /// Append a single ASCII byte. Used for structural punctuation
55 /// (`{`, `}`, `[`, `]`, `,`, `:`, `"`).
56 fn write_byte(&mut self, b: u8) -> Result<(), Self::Error>;
57
58 /// Append a single ASCII byte WITHOUT a per-write capacity check.
59 ///
60 /// # Safety
61 ///
62 /// The caller MUST have previously called [`Self::reserve_hint`] with
63 /// at least enough bytes to cover this write plus all subsequent
64 /// writes up to the next `reserve_hint` (or the end of writing).
65 /// Sinks that don't track a tail capacity (e.g. `fmt::Write`-backed)
66 /// fall back to the safe `write_byte`.
67 ///
68 /// Why this exists: under random `f64` inputs the per-element
69 /// capacity-check branch in `Vec::push` at the comma site mispredicts
70 /// at ~13% in `perf record -e branch-misses` — the single largest
71 /// mispredict source in the array hot loop. Hoisting the check via
72 /// `reserve_hint` and emitting the byte unchecked removes that
73 /// branch entirely from the inner loop.
74 ///
75 /// Default impl forwards to `write_byte` (safe but checked) so
76 /// non-`ByteSink` sinks aren't required to opt in.
77 ///
78 /// # Safety contract example (matches what `write_array` does)
79 /// ```ignore
80 /// w.reserve_hint(n * MAX + brackets_and_commas);
81 /// for _ in 0..count {
82 /// // SAFETY: reserve_hint covered all of these.
83 /// unsafe { w.write_byte_unchecked(b','); }
84 /// w.write_int_u64(...); // also covered by reserve_hint
85 /// }
86 /// ```
87 #[inline]
88 #[allow(unsafe_code)]
89 unsafe fn write_byte_unchecked(&mut self, b: u8) -> Result<(), Self::Error> {
90 self.write_byte(b)
91 }
92
93 /// Append a `&str` verbatim. Caller is responsible for any escaping
94 /// — this is the structural / pre-escaped path. For user string
95 /// payloads, call [`Self::write_escaped_str`] instead.
96 fn write_str_raw(&mut self, s: &str) -> Result<(), Self::Error>;
97
98 /// Append a JSON-quoted, escaped string (including the surrounding
99 /// `"` characters). The default impl escapes one byte at a time
100 /// through `write_byte`; sinks with bulk-write capability (like
101 /// [`StringSink`]) override with a literal-run fast path.
102 #[inline]
103 fn write_escaped_str(&mut self, s: &str) -> Result<(), Self::Error> {
104 self.write_byte(b'"')?;
105 for &b in s.as_bytes() {
106 write_escape_byte(self, b)?;
107 }
108 self.write_byte(b'"')
109 }
110
111 /// Append a byte slice whose contents are known-valid UTF-8.
112 ///
113 /// Used by macro-generated code to fuse adjacent structural literals
114 /// (e.g. `b"{\"id\":"`) into a single write. The default routes
115 /// through [`Self::write_str_raw`]; byte-oriented sinks like
116 /// [`ByteSink`] override to avoid the `&str` conversion.
117 //
118 // The `expect` is unreachable: callers feed compile-time byte-string
119 // literals from `concat!` / `b"..."`, which are ASCII by construction.
120 #[inline]
121 fn write_raw_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error> {
122 let s = core::str::from_utf8(b).expect("write_raw_bytes input must be valid UTF-8");
123 self.write_str_raw(s)
124 }
125
126 /// Write a signed 64-bit integer as a JSON number.
127 #[inline]
128 fn write_int_i64(&mut self, n: i64) -> Result<(), Self::Error> {
129 let mut buf = [0u8; 20];
130 let s = format_i64(n, &mut buf);
131 self.write_str_raw(s)
132 }
133
134 /// Write an unsigned 64-bit integer as a JSON number.
135 #[inline]
136 fn write_int_u64(&mut self, n: u64) -> Result<(), Self::Error> {
137 let mut buf = [0u8; 20];
138 let s = format_u64(n, &mut buf);
139 self.write_str_raw(s)
140 }
141
142 /// Write a signed 128-bit integer as a JSON number.
143 #[inline]
144 fn write_int_i128(&mut self, n: i128) -> Result<(), Self::Error> {
145 let mut buf = [0u8; 40];
146 let s = format_i128(n, &mut buf);
147 self.write_str_raw(s)
148 }
149
150 /// Write an unsigned 128-bit integer as a JSON number.
151 #[inline]
152 fn write_int_u128(&mut self, n: u128) -> Result<(), Self::Error> {
153 let mut buf = [0u8; 40];
154 let s = format_u128(n, &mut buf);
155 self.write_str_raw(s)
156 }
157
158 /// Write a finite `f64` as a JSON number. Non-finite inputs (`inf`,
159 /// `-inf`, `NaN`) have no JSON representation; sinks reject them
160 /// via their `Self::Error` type.
161 ///
162 /// The default routes through `core::fmt::Write` after stack-buffer
163 /// formatting via `format!` — this is the "good enough" path that
164 /// works for any sink. Sinks that want shortest-round-trip output
165 /// without fmt overhead override with a direct ryu call.
166 #[cfg(feature = "alloc")]
167 fn write_float_f64(&mut self, f: f64) -> Result<(), Self::Error>;
168
169 /// Write a finite `f64` ASSUMING the caller has reserved at least
170 /// `f64::MAX_SERIALIZED_LEN` (32) bytes of sink capacity. Skips the
171 /// per-call cap check in `Vec::extend_from_slice` — the second-largest
172 /// mispredict source after the comma `Vec::push`.
173 ///
174 /// Default impl forwards to `write_float_f64`. `ByteSink` overrides
175 /// with a raw-pointer write directly into the Vec's reserved tail.
176 ///
177 /// # Safety
178 /// `f64::MAX_SERIALIZED_LEN` (32) bytes of sink capacity must be
179 /// guaranteed available before this call. Used by `[f64]::
180 /// write_json_in_reserved` after `[T]::write_json`'s `reserve_hint`.
181 #[cfg(feature = "alloc")]
182 #[inline]
183 #[allow(unsafe_code)]
184 unsafe fn write_float_f64_unchecked(&mut self, f: f64) -> Result<(), Self::Error> {
185 self.write_float_f64(f)
186 }
187
188 /// Like `write_float_f64_unchecked`, but additionally requires the
189 /// caller to have validated that `f` is finite. The per-element
190 /// finiteness branch in the bench's hot loop was the dominant
191 /// remaining mispredict source after the cap-check elimination;
192 /// hoisting validation to a one-shot pre-scan over the whole slice
193 /// removes that branch from the inner loop entirely.
194 ///
195 /// Default impl forwards to `write_float_f64_unchecked`. `ByteSink`
196 /// overrides with the finite-known float path.
197 ///
198 /// # Safety
199 /// All preconditions of `write_float_f64_unchecked`, *plus* `f`
200 /// must be finite. Used after a successful `[f64]::pre_validate_slice`.
201 #[cfg(feature = "alloc")]
202 #[inline]
203 #[allow(unsafe_code)]
204 unsafe fn write_float_f64_unchecked_finite(&mut self, f: f64) -> Result<(), Self::Error> {
205 // SAFETY: weaker contract subsumes ours.
206 unsafe { self.write_float_f64_unchecked(f) }
207 }
208
209 /// Per-element float write that branchlessly tolerates non-finite
210 /// input. Tainted bytes (junk ASCII for non-finite values) land in
211 /// the sink; the caller is expected to query the sink's
212 /// `take_nonfinite_taint()` once at the end of the slice and turn a
213 /// non-zero result into a typed error.
214 ///
215 /// This avoids both the per-element finiteness branch AND the
216 /// pre-scan pass that hoisted it out of the loop in the previous
217 /// design (the SIMD-vectorised pand/pcmpeqd scan was costing ~20%
218 /// of total cycles per `perf record -c cycles`).
219 ///
220 /// Default impl forwards to `write_float_f64_unchecked` and emits a
221 /// dummy taint of 0 — sinks without taint tracking should never be
222 /// driven through this path.
223 ///
224 /// # Safety
225 /// `MAX_SERIALIZED_LEN` (32) bytes of sink capacity must be available.
226 #[cfg(feature = "alloc")]
227 #[inline]
228 #[allow(unsafe_code)]
229 unsafe fn write_float_f64_taint(&mut self, f: f64) -> Result<(), Self::Error> {
230 // SAFETY: weaker contract subsumes ours.
231 unsafe { self.write_float_f64_unchecked(f) }
232 }
233
234 /// Read and clear the accumulated non-finite taint from the sink.
235 /// Returns 0 for sinks without taint tracking. The array writer
236 /// queries this once after a batch of `write_float_f64_taint`
237 /// calls; a non-zero result means at least one input was inf/NaN
238 /// and the call must return `Err(NonFiniteFloat)`.
239 #[inline]
240 fn take_nonfinite_taint(&mut self) -> u64 {
241 0
242 }
243}
244
245const HEX_LOWER: [u8; 16] = *b"0123456789abcdef";
246
247/// Escape sequences for bytes 0x00–0x1F plus `"` and `\`. `0` means
248/// the byte passes through verbatim; otherwise the value is the ASCII
249/// char after `\` (e.g. `b'n'` for `\n`).
250const ESCAPE_TABLE: [u8; 256] = {
251 let mut t = [0u8; 256];
252 t[b'"' as usize] = b'"';
253 t[b'\\' as usize] = b'\\';
254 t[b'\n' as usize] = b'n';
255 t[b'\r' as usize] = b'r';
256 t[b'\t' as usize] = b't';
257 t[0x08] = b'b';
258 t[0x0C] = b'f';
259 t
260};
261
262/// Write one byte of a string body, applying JSON escape rules.
263fn write_escape_byte<W: JsonWrite + ?Sized>(w: &mut W, b: u8) -> Result<(), W::Error> {
264 let esc = ESCAPE_TABLE[b as usize];
265 if esc != 0 {
266 w.write_byte(b'\\')?;
267 return w.write_byte(esc);
268 }
269 if b < 0x20 {
270 w.write_byte(b'\\')?;
271 w.write_byte(b'u')?;
272 w.write_byte(b'0')?;
273 w.write_byte(b'0')?;
274 w.write_byte(HEX_LOWER[(b >> 4) as usize])?;
275 return w.write_byte(HEX_LOWER[(b & 0x0F) as usize]);
276 }
277 w.write_byte(b)
278}
279
280/// Returns true for bytes that need an escape sequence inside a JSON string
281/// body (quote, backslash, or any control byte `< 0x20`). Everything else —
282/// including high-bit UTF-8 continuation bytes — is safe to write verbatim.
283///
284/// Only used by the escape-writing sinks (`StringSink`, `ByteSink`,
285/// `PrettyStringSink`), all of which are alloc-gated.
286#[cfg(feature = "alloc")]
287#[inline]
288const fn needs_escape(b: u8) -> bool {
289 b == b'"' || b == b'\\' || b < 0x20
290}
291
292/// `JsonWrite` sink that appends to a `String`. Infallible.
293#[cfg(feature = "alloc")]
294#[derive(Debug)]
295pub struct StringSink<'a> {
296 out: &'a mut String,
297}
298
299#[cfg(feature = "alloc")]
300impl<'a> StringSink<'a> {
301 #[must_use]
302 pub const fn new(out: &'a mut String) -> Self {
303 Self { out }
304 }
305}
306
307#[cfg(feature = "alloc")]
308impl JsonWrite for StringSink<'_> {
309 /// `StringSink` widens its error to [`Error`] (rather than
310 /// [`core::convert::Infallible`]) so the float path has a typed
311 /// variant for non-finite inputs. The byte/string/integer writes
312 /// never fail in practice; only `write_float_f64` produces an `Err`.
313 type Error = Error;
314
315 #[inline]
316 fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
317 // SAFETY would be required for `as_mut_vec`; instead push as a char.
318 // Single-byte ASCII pushes go through `String::push` which is
319 // already an inlined `Vec::push` — no formatting overhead.
320 self.out.push(b as char);
321 Ok(())
322 }
323
324 #[inline]
325 fn write_str_raw(&mut self, s: &str) -> Result<(), Self::Error> {
326 self.out.push_str(s);
327 Ok(())
328 }
329
330 /// Literal-run fast path: scan for the next byte that needs escaping,
331 /// bulk-append the safe stretch, then emit one escape and resume.
332 /// Mirrors the `decode_escapes` strategy in `de.rs` in reverse.
333 fn write_escaped_str(&mut self, s: &str) -> Result<(), Self::Error> {
334 self.out.push('"');
335 let bytes = s.as_bytes();
336 let mut i = 0;
337 let mut start = 0;
338 while i < bytes.len() {
339 let b = bytes[i];
340 if needs_escape(b) {
341 if start < i {
342 // Safe stretch: every byte in [start..i] is either ASCII
343 // non-special or part of a valid UTF-8 multi-byte run
344 // (since `s` is `&str`, the input is valid UTF-8 and
345 // the only single-byte stop conditions are the ones
346 // `needs_escape` flags).
347 self.out.push_str(&s[start..i]);
348 }
349 write_escape_byte(self, b)?;
350 start = i + 1;
351 }
352 i += 1;
353 }
354 if start < bytes.len() {
355 self.out.push_str(&s[start..]);
356 }
357 self.out.push('"');
358 Ok(())
359 }
360
361 /// Production float path. Currently dispatches to the `write!`-based
362 /// formatter — see the `float` module below for the alternate ryu
363 /// path and the bench that picks between them. Both reject non-finite
364 /// inputs with `ErrorKind::NonFiniteFloat`.
365 #[inline]
366 fn write_float_f64(&mut self, f: f64) -> Result<(), Self::Error> {
367 float::format_f64_write(f, self.out)
368 }
369}
370
371// ---------------------------------------------------------------------------
372// ByteSink — fast path for to_string / to_vec
373// ---------------------------------------------------------------------------
374
375/// `JsonWrite` sink that appends to a `Vec<u8>`.
376///
377/// Bypasses `String`'s UTF-8 invariant maintenance — every byte
378/// written is known-valid by construction, so the caller can convert
379/// to `String` via `from_utf8_unchecked` after serialization completes.
380#[cfg(feature = "alloc")]
381#[derive(Debug)]
382pub struct ByteSink<'a> {
383 out: &'a mut alloc::vec::Vec<u8>,
384 /// Set whenever any float write encounters a non-finite input.
385 /// Read once at the end of the array path; if non-zero, the call
386 /// returns `Err(NonFiniteFloat)`. Threading this side-channel
387 /// avoids a per-element branch on the finiteness check (the
388 /// dominant remaining cliff source under random input).
389 nonfinite_taint: u64,
390}
391
392#[cfg(feature = "alloc")]
393impl<'a> ByteSink<'a> {
394 #[must_use]
395 pub const fn new(out: &'a mut alloc::vec::Vec<u8>) -> Self {
396 Self {
397 out,
398 nonfinite_taint: 0,
399 }
400 }
401}
402
403#[cfg(feature = "alloc")]
404impl JsonWrite for ByteSink<'_> {
405 type Error = Error;
406
407 #[inline]
408 fn reserve_hint(&mut self, additional: usize) {
409 self.out.reserve(additional);
410 }
411
412 #[inline]
413 fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
414 self.out.push(b);
415 Ok(())
416 }
417
418 /// Branchless byte append. Skips `Vec::push`'s capacity check; relies
419 /// on the caller having reserved enough via `reserve_hint`.
420 ///
421 /// # Safety
422 /// `self.out.len() < self.out.capacity()` must hold. The caller's
423 /// `reserve_hint` is what makes this safe in `write_array` for
424 /// primitive slices.
425 #[inline]
426 #[allow(unsafe_code)]
427 unsafe fn write_byte_unchecked(&mut self, b: u8) -> Result<(), Self::Error> {
428 // SAFETY: caller-asserted len < capacity. Equivalent to
429 // `Vec::push` minus the cap check + grow path. Writing directly
430 // to the tail and bumping len keeps the per-call cost to two
431 // memory ops with no branches.
432 unsafe {
433 let len = self.out.len();
434 let ptr = self.out.as_mut_ptr().add(len);
435 core::ptr::write(ptr, b);
436 self.out.set_len(len + 1);
437 }
438 Ok(())
439 }
440
441 #[inline]
442 fn write_str_raw(&mut self, s: &str) -> Result<(), Self::Error> {
443 self.out.extend_from_slice(s.as_bytes());
444 Ok(())
445 }
446
447 #[inline]
448 fn write_raw_bytes(&mut self, b: &[u8]) -> Result<(), Self::Error> {
449 self.out.extend_from_slice(b);
450 Ok(())
451 }
452
453 fn write_escaped_str(&mut self, s: &str) -> Result<(), Self::Error> {
454 self.out.push(b'"');
455 let bytes = s.as_bytes();
456 let mut i = 0;
457 let mut start = 0;
458 while i < bytes.len() {
459 let b = bytes[i];
460 if needs_escape(b) {
461 if start < i {
462 self.out.extend_from_slice(&bytes[start..i]);
463 }
464 write_escape_byte(self, b)?;
465 start = i + 1;
466 }
467 i += 1;
468 }
469 if start < bytes.len() {
470 self.out.extend_from_slice(&bytes[start..]);
471 }
472 self.out.push(b'"');
473 Ok(())
474 }
475
476 #[inline]
477 fn write_int_i64(&mut self, n: i64) -> Result<(), Self::Error> {
478 if n >= 0 {
479 #[allow(clippy::cast_sign_loss)]
480 return self.write_int_u64(n as u64);
481 }
482 self.out.push(b'-');
483 #[allow(clippy::cast_possible_truncation)]
484 let mag = i128::from(n).unsigned_abs() as u64;
485 self.write_int_u64(mag)
486 }
487
488 #[inline]
489 fn write_int_u64(&mut self, n: u64) -> Result<(), Self::Error> {
490 format_u64_direct(n, self.out);
491 Ok(())
492 }
493
494 #[inline]
495 fn write_int_i128(&mut self, n: i128) -> Result<(), Self::Error> {
496 let mut buf = [0u8; 40];
497 let s = format_i128(n, &mut buf);
498 self.out.extend_from_slice(s.as_bytes());
499 Ok(())
500 }
501
502 #[inline]
503 fn write_int_u128(&mut self, n: u128) -> Result<(), Self::Error> {
504 let mut buf = [0u8; 40];
505 let s = format_u128(n, &mut buf);
506 self.out.extend_from_slice(s.as_bytes());
507 Ok(())
508 }
509
510 #[inline]
511 fn write_float_f64(&mut self, f: f64) -> Result<(), Self::Error> {
512 // `format_finite_to_vec` returns false for non-finite inputs (after
513 // doing its own bit-pattern check); folding the check in there keeps
514 // `f` in `xmm0` across the call and avoids the spill/reload LLVM
515 // produced when the check sat at this call site.
516 if crate::float::format_finite_to_vec(f, self.out) {
517 Ok(())
518 } else {
519 Err(Error::new(ErrorKind::NonFiniteFloat, Position::START))
520 }
521 }
522
523 /// SAFETY-relying override: caller must have reserved ≥ 32 bytes via
524 /// `reserve_hint`. Skips `Vec::extend_from_slice`'s per-call cap
525 /// check that the per-element float loop otherwise pays.
526 ///
527 /// The non-finite arm is outlined via `#[cold]` so the branch
528 /// predictor gets a strong static hint that the error path is rare.
529 /// On `perf record -e branch-misses` the inlined `Err::new(...)`
530 /// path inflated the finiteness `jg`'s mispredict rate; outlining
531 /// it pushed the cold-arm PCs out of the hot loop's history.
532 #[inline]
533 #[allow(unsafe_code)]
534 unsafe fn write_float_f64_unchecked(&mut self, f: f64) -> Result<(), Self::Error> {
535 // SAFETY: caller-guaranteed capacity (≥ 32) is exactly what
536 // `format_finite_to_vec_unchecked` requires.
537 if unsafe { crate::float::format_finite_to_vec_unchecked(f, self.out) } {
538 Ok(())
539 } else {
540 cold_nonfinite_byte_sink()
541 }
542 }
543
544 /// Finite-known override: skip both the cap check AND the finiteness
545 /// check. The slice writer guarantees both preconditions via a
546 /// one-shot pre-scan + `reserve_hint`. This removes the last
547 /// data-dependent branch from the bench's per-element loop.
548 ///
549 /// # Safety
550 /// `cap - len ≥ 32` AND `f.is_finite()`.
551 #[inline]
552 #[allow(unsafe_code)]
553 unsafe fn write_float_f64_unchecked_finite(&mut self, f: f64) -> Result<(), Self::Error> {
554 // SAFETY: contract forwarded.
555 unsafe { crate::float::format_finite_to_vec_unchecked_finite(f, self.out) };
556 Ok(())
557 }
558
559 /// Taint-tracking float write: accumulates non-finite-ness into the
560 /// sink's `nonfinite_taint` field for end-of-slice query. No
561 /// per-element branch on finiteness.
562 #[inline]
563 #[allow(unsafe_code)]
564 unsafe fn write_float_f64_taint(&mut self, f: f64) -> Result<(), Self::Error> {
565 // SAFETY: caller-reserved ≥ 32 bytes.
566 self.nonfinite_taint |= unsafe { crate::float::format_finite_to_vec_taint(f, self.out) };
567 Ok(())
568 }
569
570 #[inline]
571 fn take_nonfinite_taint(&mut self) -> u64 {
572 core::mem::take(&mut self.nonfinite_taint)
573 }
574}
575
576/// Outlined non-finite error path for `ByteSink`. `#[cold]` biases the
577/// branch predictor's static hint toward not-taken (the always-correct
578/// guess for well-formed input) and keeps the error-construction code
579/// out of the hot icache footprint.
580#[cfg(feature = "alloc")]
581#[cold]
582#[inline(never)]
583const fn cold_nonfinite_byte_sink() -> Result<(), Error> {
584 Err(Error::new(ErrorKind::NonFiniteFloat, Position::START))
585}
586
587// ---------------------------------------------------------------------------
588// Integer formatters — forward-write with digit-count precomputation.
589// ---------------------------------------------------------------------------
590//
591// `format!("{n}")` routes through `core::fmt::Formatter`, which on profile
592// is the dominant cost when a payload is integer-heavy. Hand-rolled
593// two-digit-at-a-time formatting using a 200-byte LUT runs ~4× faster on
594// a `Vec<i64>` workload and produces byte-for-byte identical output.
595//
596// The digit count is computed upfront via `leading_zeros()` + a lookup
597// table (Champagne-Gareau & Lemire, SPE 2026), then digits are written
598// forward from the end of the buffer toward the front. This eliminates
599// the backward-fill + `ptr::copy` shift the previous implementation paid.
600
601const DIGIT_LUT: &[u8; 200] = b"\
6020001020304050607080910111213141516171819\
6032021222324252627282930313233343536373839\
6044041424344454647484950515253545556575859\
6056061626364656667686970717273747576777879\
6068081828384858687888990919293949596979899";
607
608/// Branchless digit count for a `u64`.
609///
610/// Uses `leading_zeros()` to index into a table of candidate digit counts,
611/// then one comparison resolves the off-by-one boundary. The table stores
612/// `(candidate_digit_count, threshold)` pairs: if `n > threshold` the true
613/// count is `candidate + 1`, otherwise it's `candidate`. This runs in ~3
614/// cycles on x86-64 (`lzcnt` + table load + compare).
615#[inline]
616#[allow(clippy::cast_possible_truncation)]
617fn fast_digit_count(n: u64) -> usize {
618 // Table indexed by `leading_zeros(n | 1)`. Each entry is the largest
619 // value with `digit_count` digits; if `n` exceeds it, the true count
620 // is one more. Entry 63 covers n=0 and n=1 (lzcnt=63).
621 //
622 // Built from: for each lzcnt value z, the candidate digit count is
623 // floor(log10(2^(63-z))) + 1 when the range is unambiguous, with the
624 // threshold being 10^candidate - 1.
625 // Each entry is `(candidate, threshold)` where `candidate` is the
626 // digit count when `n <= threshold`, and `candidate + 1` when
627 // `n > threshold`. For lzcnt buckets where all values share the
628 // same digit count, threshold is set to the bucket's max so the
629 // `+1` never fires.
630 static TABLE: [(u8, u64); 64] = [
631 (19, 9_999_999_999_999_999_999), // lzcnt 0: 19 or 20 digits
632 (19, 9_223_372_036_854_775_807), // lzcnt 1: always 19
633 (19, 4_611_686_018_427_387_903), // lzcnt 2: always 19
634 (19, 2_305_843_009_213_693_951), // lzcnt 3: always 19
635 (18, 999_999_999_999_999_999), // lzcnt 4: 18 or 19
636 (18, 576_460_752_303_423_487), // lzcnt 5: always 18
637 (18, 288_230_376_151_711_743), // lzcnt 6: always 18
638 (17, 99_999_999_999_999_999), // lzcnt 7: 17 or 18
639 (17, 72_057_594_037_927_935), // lzcnt 8: always 17
640 (17, 36_028_797_018_963_967), // lzcnt 9: always 17
641 (16, 9_999_999_999_999_999), // lzcnt 10: 16 or 17
642 (16, 9_007_199_254_740_991), // lzcnt 11: always 16
643 (16, 4_503_599_627_370_495), // lzcnt 12: always 16
644 (16, 2_251_799_813_685_247), // lzcnt 13: always 16
645 (15, 999_999_999_999_999), // lzcnt 14: 15 or 16
646 (15, 562_949_953_421_311), // lzcnt 15: always 15
647 (15, 281_474_976_710_655), // lzcnt 16: always 15
648 (14, 99_999_999_999_999), // lzcnt 17: 14 or 15
649 (14, 70_368_744_177_663), // lzcnt 18: always 14
650 (14, 35_184_372_088_831), // lzcnt 19: always 14
651 (13, 9_999_999_999_999), // lzcnt 20: 13 or 14
652 (13, 8_796_093_022_207), // lzcnt 21: always 13
653 (13, 4_398_046_511_103), // lzcnt 22: always 13
654 (13, 2_199_023_255_551), // lzcnt 23: always 13
655 (12, 999_999_999_999), // lzcnt 24: 12 or 13
656 (12, 549_755_813_887), // lzcnt 25: always 12
657 (12, 274_877_906_943), // lzcnt 26: always 12
658 (11, 99_999_999_999), // lzcnt 27: 11 or 12
659 (11, 68_719_476_735), // lzcnt 28: always 11
660 (11, 34_359_738_367), // lzcnt 29: always 11
661 (10, 9_999_999_999), // lzcnt 30: 10 or 11
662 (10, 8_589_934_591), // lzcnt 31: always 10
663 (10, 4_294_967_295), // lzcnt 32: always 10
664 (10, 2_147_483_647), // lzcnt 33: always 10
665 (9, 999_999_999), // lzcnt 34: 9 or 10
666 (9, 536_870_911), // lzcnt 35: always 9
667 (9, 268_435_455), // lzcnt 36: always 9
668 (8, 99_999_999), // lzcnt 37: 8 or 9
669 (8, 67_108_863), // lzcnt 38: always 8
670 (8, 33_554_431), // lzcnt 39: always 8
671 (7, 9_999_999), // lzcnt 40: 7 or 8
672 (7, 8_388_607), // lzcnt 41: always 7
673 (7, 4_194_303), // lzcnt 42: always 7
674 (7, 2_097_151), // lzcnt 43: always 7
675 (6, 999_999), // lzcnt 44: 6 or 7
676 (6, 524_287), // lzcnt 45: always 6
677 (6, 262_143), // lzcnt 46: always 6
678 (5, 99_999), // lzcnt 47: 5 or 6
679 (5, 65_535), // lzcnt 48: always 5
680 (5, 32_767), // lzcnt 49: always 5
681 (4, 9_999), // lzcnt 50: 4 or 5
682 (4, 8_191), // lzcnt 51: always 4
683 (4, 4_095), // lzcnt 52: always 4
684 (4, 2_047), // lzcnt 53: always 4
685 (3, 999), // lzcnt 54: 3 or 4
686 (3, 511), // lzcnt 55: always 3
687 (3, 255), // lzcnt 56: always 3
688 (2, 99), // lzcnt 57: 2 or 3
689 (2, 63), // lzcnt 58: always 2
690 (2, 31), // lzcnt 59: always 2
691 (1, 9), // lzcnt 60: 1 or 2
692 (1, 7), // lzcnt 61: always 1
693 (1, 3), // lzcnt 62: always 1
694 (1, 1), // lzcnt 63: always 1
695 ];
696 let lz = (n | 1).leading_zeros() as usize;
697 let (candidate, threshold) = TABLE[lz];
698 candidate as usize + usize::from(n > threshold)
699}
700
701/// Write `n` as decimal digits into `buf[0..end]`, filling from the tail
702/// toward index 0. Caller must ensure `buf` points to at least `end`
703/// writable bytes and that `end == fast_digit_count(n)`.
704#[inline]
705#[allow(clippy::cast_possible_truncation, unsafe_code)]
706fn write_digits_backward(mut n: u64, buf: *mut u8, end: usize) {
707 let mut pos = end;
708 while n >= 100 {
709 let r = (n % 100) as usize;
710 n /= 100;
711 pos -= 2;
712 // SAFETY: `pos` decreases in steps of 2 from `end` (which equals
713 // the digit count of the original `n`). The loop exits before
714 // `pos` underflows because each iteration consumes two decimal
715 // digits. Caller guarantees `buf[0..end]` is writable.
716 unsafe {
717 *buf.add(pos) = DIGIT_LUT[r * 2];
718 *buf.add(pos + 1) = DIGIT_LUT[r * 2 + 1];
719 }
720 }
721 if n >= 10 {
722 let r = n as usize;
723 pos -= 2;
724 unsafe {
725 *buf.add(pos) = DIGIT_LUT[r * 2];
726 *buf.add(pos + 1) = DIGIT_LUT[r * 2 + 1];
727 }
728 } else {
729 pos -= 1;
730 unsafe {
731 *buf.add(pos) = b'0' + n as u8;
732 }
733 }
734}
735
736/// Format a `u64` directly into a `Vec<u8>`. Precomputes digit count so
737/// digits land at their final position — no post-copy shift needed.
738#[cfg(feature = "alloc")]
739#[allow(clippy::cast_possible_truncation)]
740fn format_u64_direct(n: u64, out: &mut alloc::vec::Vec<u8>) {
741 let digits = fast_digit_count(n);
742 out.reserve(digits);
743 let old_len = out.len();
744 #[allow(unsafe_code)]
745 unsafe {
746 let base = out.as_mut_ptr().add(old_len);
747 write_digits_backward(n, base, digits);
748 out.set_len(old_len + digits);
749 }
750}
751
752/// Format a `u64` into `buf` (big-endian text). Returns the slice of
753/// `buf` that holds the digits.
754//
755// The `expect`s below are unreachable: every byte written is from
756// `DIGIT_LUT` (ASCII '0'..='9') or the literal `b'-'`. They are
757// monomorphization-time guards against a formatter-internal bug.
758#[allow(clippy::cast_possible_truncation)]
759fn format_u64(n: u64, buf: &mut [u8; 20]) -> &str {
760 let digits = fast_digit_count(n);
761 write_digits_backward(n, buf.as_mut_ptr(), digits);
762 core::str::from_utf8(&buf[..digits]).expect("integer formatter emits ASCII")
763}
764
765fn format_i64(n: i64, buf: &mut [u8; 20]) -> &str {
766 if n >= 0 {
767 #[allow(clippy::cast_sign_loss)]
768 return format_u64(n as u64, buf);
769 }
770 #[allow(clippy::cast_possible_truncation)]
771 let mag = i128::from(n).unsigned_abs() as u64;
772 let digits = fast_digit_count(mag);
773 buf[0] = b'-';
774 write_digits_backward(mag, buf[1..].as_mut_ptr(), digits);
775 let total = 1 + digits;
776 core::str::from_utf8(&buf[..total]).expect("integer formatter emits ASCII")
777}
778
779#[allow(clippy::cast_possible_truncation)]
780fn format_u128(mut n: u128, buf: &mut [u8; 40]) -> &str {
781 let mut pos = buf.len();
782 while n >= 100 {
783 let r = (n % 100) as usize;
784 n /= 100;
785 pos -= 2;
786 buf[pos] = DIGIT_LUT[r * 2];
787 buf[pos + 1] = DIGIT_LUT[r * 2 + 1];
788 }
789 if n >= 10 {
790 let r = n as usize;
791 pos -= 2;
792 buf[pos] = DIGIT_LUT[r * 2];
793 buf[pos + 1] = DIGIT_LUT[r * 2 + 1];
794 } else {
795 pos -= 1;
796 buf[pos] = b'0' + n as u8;
797 }
798 core::str::from_utf8(&buf[pos..]).expect("integer formatter emits ASCII")
799}
800
801fn format_i128(n: i128, buf: &mut [u8; 40]) -> &str {
802 if n >= 0 {
803 #[allow(clippy::cast_sign_loss)]
804 return format_u128(n as u128, buf);
805 }
806 let mag = n.unsigned_abs();
807 let mut tmp = [0u8; 40];
808 let s = format_u128(mag, &mut tmp);
809 let len = s.len();
810 buf[0] = b'-';
811 buf[1..=len].copy_from_slice(s.as_bytes());
812 let total = 1 + len;
813 core::str::from_utf8(&buf[..total]).expect("integer formatter emits ASCII")
814}
815
816// ---------------------------------------------------------------------------
817// Float formatting.
818//
819// `format_f64_write` dispatches to the in-tree Grisu3 formatter
820// (`crate::float::format_finite`), which falls back to libstd's
821// `Display for f64` on the ~0.5% of inputs where Grisu3 cannot prove
822// its output is the shortest round-trip representation.
823//
824// The bench in `bourne-bench/floats` pins this entry point by name.
825// ---------------------------------------------------------------------------
826
827#[cfg(feature = "alloc")]
828pub mod float {
829 //! Public so the head-to-head bench in `bourne-bench` can pin the
830 //! production formatter by name; not part of the documented API
831 //! surface.
832
833 use super::{Error, ErrorKind, Position};
834 use alloc::string::String;
835
836 /// Reject `inf` / `-inf` / `NaN` with a typed error. Position is
837 /// `START` because serializer errors don't have an input byte to
838 /// point at — symmetric with how the parse side reports
839 /// "byte offset" errors.
840 #[inline]
841 const fn reject_non_finite(f: f64) -> Result<(), Error> {
842 if f.is_finite() {
843 Ok(())
844 } else {
845 Err(Error::new(ErrorKind::NonFiniteFloat, Position::START))
846 }
847 }
848
849 /// Production float formatter. Delegates correctness to the
850 /// in-tree Grisu3 implementation (`crate::float`). Non-finite
851 /// inputs are rejected before any digit work happens.
852 #[inline]
853 pub fn format_f64_write(f: f64, out: &mut String) -> Result<(), Error> {
854 reject_non_finite(f)?;
855 crate::float::format_finite(f, out);
856 Ok(())
857 }
858}
859
860// ---------------------------------------------------------------------------
861// ToJson trait + entry points
862// ---------------------------------------------------------------------------
863
864/// Types that know how to serialize themselves to a [`JsonWrite`] sink.
865///
866/// This is the dual of [`crate::FromJson`]. Implementors call sink methods
867/// directly — there is no intermediate `Value` representation.
868pub trait ToJson {
869 /// Lower bound on the number of bytes `write_json` will emit.
870 ///
871 /// Used by [`to_vec`] / [`to_string`] to size the initial allocation.
872 /// Defaults to `0` so existing impls aren't forced to provide it.
873 const MIN_SERIALIZED_LEN: usize = 0;
874
875 /// Upper bound on the bytes a single `write_json` call emits, used by
876 /// sequence impls to pre-reserve buffer capacity. `0` means "no useful
877 /// upper bound" — sequence impls skip the reservation in that case.
878 ///
879 /// Primitives with a known maximum output length (floats, ints, bools)
880 /// override this. Variable-length types (`str`, `Vec<T>`, structs) keep
881 /// the default, since their per-element size depends on payload.
882 const MAX_SERIALIZED_LEN: usize = 0;
883
884 /// Serialize `self` into `w`.
885 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error>;
886
887 /// Serialize `self` into `w` ASSUMING the caller has already reserved
888 /// at least `MAX_SERIALIZED_LEN` writable bytes in the sink. Used by
889 /// [`[T]::write_json`] after its `reserve_hint` call so the per-element
890 /// path skips its own cap checks.
891 ///
892 /// Default impl forwards to `write_json` (safe + checked). Primitives
893 /// override with sink-specific unchecked variants. For `MAX_SERIALIZED_LEN
894 /// == 0` types this method is never called.
895 ///
896 /// # Safety
897 /// `Self::MAX_SERIALIZED_LEN` bytes of sink capacity must be guaranteed
898 /// available before this call. For types where
899 /// [`Self::NEEDS_VALIDATION`] is `true`, [`Self::pre_validate_slice`]
900 /// must have been called on the enclosing slice (and returned `Ok`)
901 /// before invoking this method, so the per-element write can assume
902 /// the type's validity precondition (e.g. finiteness for floats).
903 #[inline]
904 #[allow(unsafe_code)]
905 unsafe fn write_json_in_reserved<W: JsonWrite + ?Sized>(
906 &self,
907 w: &mut W,
908 ) -> Result<(), W::Error> {
909 self.write_json(w)
910 }
911
912 /// Whether this type requires a pre-scan validation pass before
913 /// the per-element fast path can be entered. Defaults to `false`.
914 ///
915 /// `f64` / `f32` set this to `true` so the slice writer scans the
916 /// whole slice once for non-finite values and rejects up front.
917 /// The inner element-by-element loop then runs branchless w.r.t.
918 /// finiteness — measured 13.43% of all mispredicts before the
919 /// pre-scan was added.
920 const NEEDS_VALIDATION: bool = false;
921
922 /// Validate that every element of `slice` satisfies the type's
923 /// per-element precondition (e.g. finiteness). Called by the slice
924 /// writer ONCE before the per-element loop when `NEEDS_VALIDATION`
925 /// is `true`.
926 ///
927 /// Default impl is a no-op. The `Result` carries `crate::Error`
928 /// directly because all current validators reject with that type;
929 /// callers that want a different sink error must map.
930 ///
931 /// `slice` is `&[Self]` rather than `&[T]` because the validator
932 /// always sees a homogeneous slice (it's invoked from the slice
933 /// impl). On non-slice paths this method is never called.
934 #[inline]
935 fn pre_validate_slice(slice: &[Self]) -> Result<(), Error>
936 where
937 Self: Sized,
938 {
939 let _ = slice;
940 Ok(())
941 }
942}
943
944/// Serialize `value` into a fresh `String`.
945///
946/// Returns [`Error`] (rather than `Infallible`) so non-finite floats and
947/// other typed-level failures have a place to surface.
948///
949/// Internally serializes into a `Vec<u8>` via [`ByteSink`] (avoiding
950/// per-byte UTF-8 invariant checks), then converts to `String` in one
951/// step. The resulting bytes are guaranteed valid UTF-8 because every
952/// `JsonWrite` method only emits ASCII structural bytes, `&str` slices
953/// (valid by construction), ASCII escape sequences, and ASCII digit
954/// sequences from the integer/float formatters.
955// `expect` below is unreachable from user input: every `JsonWrite` path
956// emits valid UTF-8 by construction (see `ByteSink`'s impl). The check
957// guards against a serializer-internal bug, not a user-triggerable case.
958#[cfg(feature = "alloc")]
959#[allow(clippy::missing_panics_doc)]
960pub fn to_string<T: ToJson + ?Sized>(value: &T) -> Result<String, Error> {
961 let bytes = to_vec(value)?;
962 Ok(String::from_utf8(bytes).expect("json-bourne emits only valid UTF-8"))
963}
964
965/// Serialize `value` into a fresh `Vec<u8>`.
966///
967/// This is the primary fast path: writes directly into `Vec<u8>` via
968/// [`ByteSink`], bypassing `String`'s per-write UTF-8 invariant checks.
969#[cfg(feature = "alloc")]
970pub fn to_vec<T: ToJson + ?Sized>(value: &T) -> Result<alloc::vec::Vec<u8>, Error> {
971 let cap = if T::MIN_SERIALIZED_LEN > 128 {
972 T::MIN_SERIALIZED_LEN
973 } else {
974 128
975 };
976 let mut out = alloc::vec::Vec::with_capacity(cap);
977 let mut sink = ByteSink::new(&mut out);
978 value.write_json(&mut sink)?;
979 Ok(out)
980}
981
982// ---------------------------------------------------------------------------
983// fmt::Write sink
984// ---------------------------------------------------------------------------
985
986/// `JsonWrite` sink that forwards to any `core::fmt::Write` implementor.
987///
988/// Useful when the destination is something other than a `String` —
989/// `&mut String` is the obvious case, but any `fmt::Write` works (a
990/// `Formatter`, a custom buffered writer, a tracing-style accumulator).
991///
992/// The error type is [`core::fmt::Error`] for byte/string writes and
993/// [`Error`] for the float path; the unified sink-level error is
994/// [`Error`], with `core::fmt::Error` mapped to a generic write
995/// failure.
996#[cfg(feature = "alloc")]
997#[derive(Debug)]
998pub struct FmtWriteSink<'a, W: ?Sized> {
999 out: &'a mut W,
1000}
1001
1002#[cfg(feature = "alloc")]
1003impl<'a, W: core::fmt::Write + ?Sized> FmtWriteSink<'a, W> {
1004 pub const fn new(out: &'a mut W) -> Self {
1005 Self { out }
1006 }
1007}
1008
1009#[cfg(feature = "alloc")]
1010impl<W: core::fmt::Write + ?Sized> JsonWrite for FmtWriteSink<'_, W> {
1011 type Error = Error;
1012
1013 #[inline]
1014 fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
1015 self.out
1016 .write_char(b as char)
1017 .map_err(|_| fmt_write_error())
1018 }
1019
1020 #[inline]
1021 fn write_str_raw(&mut self, s: &str) -> Result<(), Self::Error> {
1022 self.out.write_str(s).map_err(|_| fmt_write_error())
1023 }
1024
1025 #[inline]
1026 fn write_float_f64(&mut self, f: f64) -> Result<(), Self::Error> {
1027 if !f.is_finite() {
1028 return Err(Error::new(ErrorKind::NonFiniteFloat, Position::START));
1029 }
1030 crate::float::format_finite_fmt(f, self.out).map_err(|_| fmt_write_error())
1031 }
1032}
1033
1034/// `fmt::Write` errors don't carry detail. Map to a typed parse-style
1035/// error so callers can distinguish the failure mode without losing
1036/// the trait's error contract.
1037#[cfg(feature = "alloc")]
1038#[inline]
1039const fn fmt_write_error() -> Error {
1040 Error::new(ErrorKind::TypeMismatch, Position::START)
1041}
1042
1043// ---------------------------------------------------------------------------
1044// io::Write sink (std-only)
1045// ---------------------------------------------------------------------------
1046
1047/// `JsonWrite` sink that forwards to any `std::io::Write` implementor.
1048///
1049/// The natural target for serializing JSON to a file, socket, or other
1050/// byte stream. Errors propagate through the sink's `Self::Error`,
1051/// which is [`std::io::Error`].
1052#[cfg(feature = "std")]
1053#[derive(Debug)]
1054pub struct IoWriteSink<'a, W: ?Sized> {
1055 out: &'a mut W,
1056}
1057
1058#[cfg(feature = "std")]
1059impl<'a, W: std::io::Write + ?Sized> IoWriteSink<'a, W> {
1060 pub const fn new(out: &'a mut W) -> Self {
1061 Self { out }
1062 }
1063}
1064
1065/// Adapter that lets `core::fmt::Write` write into an `io::Write` sink.
1066/// `format_finite_fmt` only needs `fmt::Write`; this carries the
1067/// underlying I/O error out so the float path's failures don't get
1068/// flattened into a generic "fmt failed".
1069#[cfg(feature = "std")]
1070struct IoFmtAdapter<'a, W: std::io::Write + ?Sized> {
1071 inner: &'a mut W,
1072 err: Option<std::io::Error>,
1073}
1074
1075#[cfg(feature = "std")]
1076impl<W: std::io::Write + ?Sized> core::fmt::Write for IoFmtAdapter<'_, W> {
1077 fn write_str(&mut self, s: &str) -> core::fmt::Result {
1078 match self.inner.write_all(s.as_bytes()) {
1079 Ok(()) => Ok(()),
1080 Err(e) => {
1081 self.err = Some(e);
1082 Err(core::fmt::Error)
1083 }
1084 }
1085 }
1086}
1087
1088#[cfg(feature = "std")]
1089impl<W: std::io::Write + ?Sized> JsonWrite for IoWriteSink<'_, W> {
1090 type Error = std::io::Error;
1091
1092 #[inline]
1093 fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
1094 self.out.write_all(&[b])
1095 }
1096
1097 #[inline]
1098 fn write_str_raw(&mut self, s: &str) -> Result<(), Self::Error> {
1099 self.out.write_all(s.as_bytes())
1100 }
1101
1102 #[inline]
1103 fn write_float_f64(&mut self, f: f64) -> Result<(), Self::Error> {
1104 if !f.is_finite() {
1105 return Err(std::io::Error::new(
1106 std::io::ErrorKind::InvalidData,
1107 "non-finite float not representable in JSON",
1108 ));
1109 }
1110 let mut adapter = IoFmtAdapter {
1111 inner: self.out,
1112 err: None,
1113 };
1114 match crate::float::format_finite_fmt(f, &mut adapter) {
1115 Ok(()) => Ok(()),
1116 Err(_) => Err(adapter
1117 .err
1118 .unwrap_or_else(|| std::io::Error::other("fmt error in float formatter"))),
1119 }
1120 }
1121}
1122
1123/// Serialize `value` directly into a [`std::io::Write`] sink.
1124///
1125/// The standard "stream JSON to a file/socket" entry point. For an
1126/// in-memory build use [`to_string`] / [`to_vec`].
1127#[cfg(feature = "std")]
1128pub fn to_writer<T: ToJson + ?Sized, W: std::io::Write>(
1129 value: &T,
1130 writer: &mut W,
1131) -> Result<(), std::io::Error> {
1132 let mut sink = IoWriteSink::new(writer);
1133 value.write_json(&mut sink)
1134}
1135
1136/// Serialize `value` into any [`core::fmt::Write`] sink.
1137///
1138/// Returns the underlying [`Error`] (typed) on failure — including
1139/// non-finite floats. For `String` targets prefer [`to_string`]; this
1140/// entry point is for arbitrary `fmt::Write` consumers.
1141#[cfg(feature = "alloc")]
1142pub fn to_fmt<T: ToJson + ?Sized, W: core::fmt::Write + ?Sized>(
1143 value: &T,
1144 writer: &mut W,
1145) -> Result<(), Error> {
1146 let mut sink = FmtWriteSink::new(writer);
1147 value.write_json(&mut sink)
1148}
1149
1150// ---------------------------------------------------------------------------
1151// Pretty-print sink
1152// ---------------------------------------------------------------------------
1153
1154/// `JsonWrite` sink that emits indented, multi-line JSON.
1155///
1156/// Wraps a `String` and tracks container depth + a one-byte lookahead
1157/// (`pending_open`). When an opener (`[` / `{`) is followed immediately
1158/// by the matching closer (no contents), the sink emits the compact
1159/// form `[]` / `{}`. Otherwise it inserts a newline plus the current
1160/// indent before each element and before the closing bracket.
1161///
1162/// Indent unit defaults to two spaces; configure via [`Self::with_indent`].
1163#[cfg(feature = "alloc")]
1164#[derive(Debug)]
1165pub struct PrettyStringSink<'a> {
1166 out: &'a mut String,
1167 indent: &'static str,
1168 depth: usize,
1169 /// `Some(b)` when we wrote `[` or `{` and haven't yet decided
1170 /// whether the container is empty. The next structural byte
1171 /// resolves it: a matching close → emit `[]` / `{}`; anything
1172 /// else → flush the open + newline + indent for the first
1173 /// element, then continue.
1174 pending_open: Option<u8>,
1175}
1176
1177#[cfg(feature = "alloc")]
1178impl<'a> PrettyStringSink<'a> {
1179 /// Build a pretty sink writing to `out` with the default 2-space
1180 /// indent.
1181 #[must_use]
1182 pub const fn new(out: &'a mut String) -> Self {
1183 Self {
1184 out,
1185 indent: " ",
1186 depth: 0,
1187 pending_open: None,
1188 }
1189 }
1190
1191 /// Build a pretty sink with a custom indent string. Pass `"\t"`
1192 /// for tabs, `" "` for four spaces, etc. The indent must be
1193 /// pure whitespace — JSON doesn't validate it on the wire, but
1194 /// emitting non-whitespace would corrupt the output.
1195 #[must_use]
1196 pub const fn with_indent(out: &'a mut String, indent: &'static str) -> Self {
1197 Self {
1198 out,
1199 indent,
1200 depth: 0,
1201 pending_open: None,
1202 }
1203 }
1204
1205 /// Resolve any pending open by emitting it and dropping the
1206 /// pending state. Used before writing any non-structural byte
1207 /// (a value's first byte).
1208 fn flush_pending_open(&mut self) {
1209 if let Some(b) = self.pending_open.take() {
1210 self.out.push(b as char);
1211 self.depth += 1;
1212 self.newline_and_indent();
1213 }
1214 }
1215
1216 fn newline_and_indent(&mut self) {
1217 self.out.push('\n');
1218 for _ in 0..self.depth {
1219 self.out.push_str(self.indent);
1220 }
1221 }
1222}
1223
1224#[cfg(feature = "alloc")]
1225impl JsonWrite for PrettyStringSink<'_> {
1226 type Error = Error;
1227
1228 fn write_byte(&mut self, b: u8) -> Result<(), Self::Error> {
1229 match b {
1230 b'[' | b'{' => {
1231 // Resolve any prior pending open: the prior container
1232 // is non-empty, so emit it + indent for our position.
1233 self.flush_pending_open();
1234 // Defer this open — we don't know yet if it's empty.
1235 self.pending_open = Some(b);
1236 Ok(())
1237 }
1238 b']' | b'}' => {
1239 if let Some(open) = self.pending_open.take() {
1240 // Empty container: write the open and matching
1241 // close back-to-back with no whitespace.
1242 self.out.push(open as char);
1243 self.out.push(b as char);
1244 return Ok(());
1245 }
1246 self.depth -= 1;
1247 self.newline_and_indent();
1248 self.out.push(b as char);
1249 Ok(())
1250 }
1251 b',' => {
1252 // After a value inside a container: newline + indent
1253 // before the next element. The pending_open state
1254 // can't be live here — we must have written at least
1255 // one value to be at a comma.
1256 debug_assert!(self.pending_open.is_none());
1257 self.out.push(',');
1258 self.newline_and_indent();
1259 Ok(())
1260 }
1261 b':' => {
1262 // Object key/value separator. JSON-pretty convention
1263 // is `key: value` (one space after the colon, none
1264 // before).
1265 self.out.push_str(": ");
1266 Ok(())
1267 }
1268 _ => {
1269 // Any other single byte (rare via this entry — most
1270 // bulk text comes through write_str_raw or
1271 // write_escaped_str).
1272 self.flush_pending_open();
1273 self.out.push(b as char);
1274 Ok(())
1275 }
1276 }
1277 }
1278
1279 fn write_str_raw(&mut self, s: &str) -> Result<(), Self::Error> {
1280 if s.is_empty() {
1281 return Ok(());
1282 }
1283 self.flush_pending_open();
1284 self.out.push_str(s);
1285 Ok(())
1286 }
1287
1288 fn write_escaped_str(&mut self, s: &str) -> Result<(), Self::Error> {
1289 self.flush_pending_open();
1290 // Reuse the StringSink escape walk by constructing one
1291 // transiently. The borrow lasts only for this call.
1292 let mut inner = StringSink::new(self.out);
1293 inner.write_escaped_str(s)
1294 }
1295
1296 fn write_float_f64(&mut self, f: f64) -> Result<(), Self::Error> {
1297 self.flush_pending_open();
1298 float::format_f64_write(f, self.out)
1299 }
1300}
1301
1302/// Pretty-printed equivalent of [`to_string`]. Two-space indent, one
1303/// space after `:`, newline between every element. Empty containers
1304/// are kept compact (`[]` / `{}`).
1305#[cfg(feature = "alloc")]
1306pub fn to_string_pretty<T: ToJson + ?Sized>(value: &T) -> Result<String, Error> {
1307 let mut out = String::with_capacity(256);
1308 let mut sink = PrettyStringSink::new(&mut out);
1309 value.write_json(&mut sink)?;
1310 Ok(out)
1311}
1312
1313// ---------------------------------------------------------------------------
1314// Primitive impls
1315// ---------------------------------------------------------------------------
1316
1317impl ToJson for bool {
1318 const MIN_SERIALIZED_LEN: usize = 4; // "true"
1319 const MAX_SERIALIZED_LEN: usize = 5; // "false"
1320 #[inline]
1321 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1322 w.write_str_raw(if *self { "true" } else { "false" })
1323 }
1324}
1325
1326impl ToJson for () {
1327 const MIN_SERIALIZED_LEN: usize = 4; // "null"
1328 const MAX_SERIALIZED_LEN: usize = 4;
1329 #[inline]
1330 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1331 w.write_str_raw("null")
1332 }
1333}
1334
1335impl ToJson for str {
1336 const MIN_SERIALIZED_LEN: usize = 2; // "\"\""
1337 #[inline]
1338 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1339 w.write_escaped_str(self)
1340 }
1341}
1342
1343// `&str` is the form callers actually hold. The blanket `&T` impl below
1344// covers it via the `str` impl above — no explicit `&str` arm needed.
1345
1346macro_rules! impl_int_signed {
1347 ($($t:ty),* $(,)?) => {
1348 $(
1349 impl ToJson for $t {
1350 const MIN_SERIALIZED_LEN: usize = 1;
1351 // sign + digits. i64::MIN is "-9223372036854775808" = 20 chars.
1352 const MAX_SERIALIZED_LEN: usize = 20;
1353 #[inline]
1354 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1355 w.write_int_i64(i64::from(*self))
1356 }
1357 }
1358 )*
1359 };
1360}
1361
1362macro_rules! impl_int_unsigned {
1363 ($($t:ty),* $(,)?) => {
1364 $(
1365 impl ToJson for $t {
1366 const MIN_SERIALIZED_LEN: usize = 1;
1367 // u64::MAX = "18446744073709551615" = 20 chars.
1368 const MAX_SERIALIZED_LEN: usize = 20;
1369 #[inline]
1370 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1371 w.write_int_u64(u64::from(*self))
1372 }
1373 }
1374 )*
1375 };
1376}
1377
1378impl_int_signed!(i8, i16, i32, i64);
1379impl_int_unsigned!(u8, u16, u32, u64);
1380
1381// `isize` / `usize` widen to 64-bit on the platforms json-bourne supports.
1382// `as` is acceptable here: the cast is the documented platform widening,
1383// not a truncation.
1384#[allow(clippy::cast_possible_wrap, clippy::cast_lossless, clippy::use_self)]
1385impl ToJson for isize {
1386 const MIN_SERIALIZED_LEN: usize = 1;
1387 const MAX_SERIALIZED_LEN: usize = 20;
1388 #[inline]
1389 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1390 w.write_int_i64(*self as i64)
1391 }
1392}
1393
1394#[allow(clippy::cast_lossless, clippy::use_self)]
1395impl ToJson for usize {
1396 const MIN_SERIALIZED_LEN: usize = 1;
1397 const MAX_SERIALIZED_LEN: usize = 20;
1398 #[inline]
1399 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1400 w.write_int_u64(*self as u64)
1401 }
1402}
1403
1404impl ToJson for i128 {
1405 const MIN_SERIALIZED_LEN: usize = 1;
1406 // sign + 39 digits for i128::MIN.
1407 const MAX_SERIALIZED_LEN: usize = 40;
1408 #[inline]
1409 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1410 w.write_int_i128(*self)
1411 }
1412}
1413
1414impl ToJson for u128 {
1415 const MIN_SERIALIZED_LEN: usize = 1;
1416 // u128::MAX has 39 digits.
1417 const MAX_SERIALIZED_LEN: usize = 39;
1418 #[inline]
1419 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1420 w.write_int_u128(*self)
1421 }
1422}
1423
1424// Composite: Option<T> writes `null` for None or the inner value for Some.
1425// Mirrors the FromJson direction.
1426impl<T: ToJson> ToJson for Option<T> {
1427 const MIN_SERIALIZED_LEN: usize = 4; // "null"
1428 #[inline]
1429 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1430 match self {
1431 None => w.write_str_raw("null"),
1432 Some(v) => v.write_json(w),
1433 }
1434 }
1435}
1436
1437// References pass through. `&T: ToJson` whenever `T: ToJson` lets callers
1438// pass `&value` or `&&value` indifferently.
1439impl<T: ToJson + ?Sized> ToJson for &T {
1440 const MIN_SERIALIZED_LEN: usize = T::MIN_SERIALIZED_LEN;
1441 #[inline]
1442 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1443 (*self).write_json(w)
1444 }
1445}
1446
1447// Slices and fixed-size arrays serialize as JSON arrays. The shared
1448// helper writes the bracketed comma-separated body so impls for Vec,
1449// slice, and [T; N] don't drift on punctuation.
1450//
1451// `vec_write_json` is the symmetric hook to FromJson's `vec_from_lex`:
1452// types like `i64` / `&str` whose per-element write goes through method
1453// dispatch can override it to write directly through the sink. Default
1454// loops `T::write_json` per element.
1455fn write_array<T: ToJson, I: IntoIterator<Item = T>, W: JsonWrite + ?Sized>(
1456 iter: I,
1457 w: &mut W,
1458) -> Result<(), W::Error> {
1459 w.write_byte(b'[')?;
1460 // Peel the first element so the inner loop never re-evaluates a
1461 // `first` flag — every subsequent element unconditionally writes
1462 // `,` then itself. Saves one branch per element on hot Vec/slice
1463 // paths where the iterator is `ExactSizeIterator` and the compiler
1464 // can hoist the check out of the loop.
1465 let mut iter = iter.into_iter();
1466 if let Some(v) = iter.next() {
1467 v.write_json(w)?;
1468 for v in iter {
1469 w.write_byte(b',')?;
1470 v.write_json(w)?;
1471 }
1472 }
1473 w.write_byte(b']')
1474}
1475
1476/// Like `write_array`, but emits the brackets and commas via
1477/// `write_byte_unchecked`. Callable only after `reserve_hint` has been
1478/// invoked with at least `2 + len + len * MAX_SERIALIZED_LEN` bytes.
1479///
1480/// Why this exists: `perf record -e branch-misses` on the n=10000
1481/// float-array workload identified the comma capacity-check branch
1482/// (`cmp len, cap; jne grow`) as the single largest mispredict source —
1483/// 12.96% of all mispredicts in `to_string`. Hoisting the capacity check
1484/// via `reserve_hint` and emitting the comma unchecked removes that
1485/// branch from the inner loop entirely.
1486///
1487/// # Safety
1488/// Caller must have reserved enough sink capacity to cover `2 + len +
1489/// per_elem_max * len` bytes after the call to `reserve_hint`.
1490#[allow(unsafe_code)]
1491unsafe fn write_array_reserved<T: ToJson, W: JsonWrite + ?Sized>(
1492 slice: &[T],
1493 w: &mut W,
1494) -> Result<(), W::Error> {
1495 // SAFETY: brackets fit in the reserved space (we accounted for 2
1496 // bracket bytes in the hint). Per-element writes also fit because
1497 // each is bounded by `MAX_SERIALIZED_LEN + 1` (the +1 covers the
1498 // comma).
1499 unsafe {
1500 w.write_byte_unchecked(b'[')?;
1501 }
1502 if let Some((first, rest)) = slice.split_first() {
1503 // SAFETY: `MAX_SERIALIZED_LEN` was included in the caller's
1504 // `reserve_hint`, so each element write fits in the reserved
1505 // tail.
1506 unsafe { first.write_json_in_reserved(w) }?;
1507 for v in rest {
1508 // SAFETY: see function-level safety contract.
1509 unsafe { w.write_byte_unchecked(b',') }?;
1510 unsafe { v.write_json_in_reserved(w) }?;
1511 }
1512 }
1513 // SAFETY: same as the opening bracket.
1514 unsafe { w.write_byte_unchecked(b']') }
1515}
1516
1517impl<T: ToJson> ToJson for [T] {
1518 const MIN_SERIALIZED_LEN: usize = 2; // "[]"
1519 #[inline]
1520 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1521 // Pre-reserve when the element type has a known upper bound. The
1522 // hint covers brackets (`[`, `]`), commas (1 per element), and
1523 // the per-element max payload. After this, *every* byte the
1524 // primitive path writes fits in the reserved tail.
1525 //
1526 // The float-error surfacing below only matters under `alloc` — both
1527 // `NEEDS_VALIDATION` and the taint flag are only ever set true by
1528 // float impls, which live in the alloc-gated module. In `no_std`
1529 // builds the branches are statically unreachable; gating them also
1530 // keeps the `write_float_f64` reference (alloc-only method) out of
1531 // the no_std build.
1532 if T::MAX_SERIALIZED_LEN != 0 {
1533 #[cfg(feature = "alloc")]
1534 {
1535 // Optional pre-scan validation (kept for types that prefer
1536 // a separate validation pass; floats now use per-element
1537 // taint via `write_float_f64_taint` instead).
1538 if T::NEEDS_VALIDATION && T::pre_validate_slice(self).is_err() {
1539 return w.write_float_f64(f64::NAN);
1540 }
1541 }
1542 let hint = self
1543 .len()
1544 .saturating_mul(T::MAX_SERIALIZED_LEN.saturating_add(1))
1545 .saturating_add(2);
1546 w.reserve_hint(hint);
1547 // SAFETY: hint above covers brackets + per-element max +
1548 // commas. The sink's `write_byte_unchecked` skips the
1549 // capacity check.
1550 #[allow(unsafe_code)]
1551 let res = unsafe { write_array_reserved(self, w) };
1552 #[cfg(feature = "alloc")]
1553 {
1554 // Flush any per-element float taint. Non-zero means at least
1555 // one input was non-finite during the loop; surface it as
1556 // a typed error through the sink's natural channel.
1557 let taint = w.take_nonfinite_taint();
1558 if taint != 0 {
1559 return w.write_float_f64(f64::NAN);
1560 }
1561 }
1562 return res;
1563 }
1564 write_array(self.iter(), w)
1565 }
1566}
1567
1568impl<T: ToJson, const N: usize> ToJson for [T; N] {
1569 const MIN_SERIALIZED_LEN: usize = 2; // "[]"
1570 #[inline]
1571 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1572 (self as &[T]).write_json(w)
1573 }
1574}
1575
1576// Tuples serialize as fixed-length heterogeneous arrays. Mirrors the
1577// FromJson side which accepts `[T, U, V]` for `(T, U, V)`.
1578//
1579// The macro takes the *first* element separately from the rest so the
1580// comma placement is unambiguous: emit the first, then for each rest
1581// element emit `,` followed by it. No trailing comma, no double-walk.
1582macro_rules! impl_tuple_to_json {
1583 ($first_idx:tt: $First:ident $(, $idx:tt: $T:ident)* $(,)?) => {
1584 impl<$First: ToJson $(, $T: ToJson)*> ToJson for ($First, $($T,)*) {
1585 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1586 w.write_byte(b'[')?;
1587 self.$first_idx.write_json(w)?;
1588 $(
1589 w.write_byte(b',')?;
1590 self.$idx.write_json(w)?;
1591 )*
1592 w.write_byte(b']')
1593 }
1594 }
1595 };
1596}
1597
1598impl_tuple_to_json!(0: A);
1599impl_tuple_to_json!(0: A, 1: B);
1600impl_tuple_to_json!(0: A, 1: B, 2: C);
1601impl_tuple_to_json!(0: A, 1: B, 2: C, 3: D);
1602impl_tuple_to_json!(0: A, 1: B, 2: C, 3: D, 4: E);
1603impl_tuple_to_json!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
1604
1605// ---------------------------------------------------------------------------
1606// alloc-gated impls
1607// ---------------------------------------------------------------------------
1608
1609#[cfg(feature = "alloc")]
1610mod alloc_impls {
1611 use super::{Error, JsonWrite, ToJson};
1612 use alloc::borrow::Cow;
1613 use alloc::boxed::Box;
1614 use alloc::rc::Rc;
1615 use alloc::string::String;
1616 use alloc::sync::Arc;
1617
1618 impl ToJson for String {
1619 const MIN_SERIALIZED_LEN: usize = 2; // "\"\""
1620 #[inline]
1621 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1622 w.write_escaped_str(self.as_str())
1623 }
1624 }
1625
1626 impl ToJson for Cow<'_, str> {
1627 const MIN_SERIALIZED_LEN: usize = 2; // "\"\""
1628 #[inline]
1629 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1630 w.write_escaped_str(self.as_ref())
1631 }
1632 }
1633
1634 impl ToJson for f64 {
1635 const MIN_SERIALIZED_LEN: usize = 1;
1636 // Worst-case f64 string: sign + 17 digits + '.' + 'e' + sign +
1637 // 3-digit exponent = 25 bytes. Round to 32 to match the
1638 // formatter's stack-buffer size.
1639 const MAX_SERIALIZED_LEN: usize = 32;
1640 // Validation is folded into the per-element `write_float_f64_taint`
1641 // path: each call OR-accumulates a taint bit into the sink, and
1642 // the slice writer queries it once at the end. Skips the
1643 // separate pre-scan pass (which was ~20% of total cycles).
1644 const NEEDS_VALIDATION: bool = false;
1645 #[inline]
1646 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1647 w.write_float_f64(*self)
1648 }
1649 /// SAFETY: caller (`write_array_reserved`) has reserved
1650 /// `MAX_SERIALIZED_LEN` bytes via `reserve_hint`. Non-finite
1651 /// inputs do not break safety — they are routed through the
1652 /// sink's taint-tracking float write, which substitutes a
1653 /// finite placeholder before calling teju and accumulates a
1654 /// taint bit. The slice writer queries the sink's taint after
1655 /// the loop and converts it to an error.
1656 #[inline]
1657 #[allow(unsafe_code)]
1658 unsafe fn write_json_in_reserved<W: JsonWrite + ?Sized>(
1659 &self,
1660 w: &mut W,
1661 ) -> Result<(), W::Error> {
1662 // SAFETY: forwarded to the sink's taint-tracking float path.
1663 unsafe { w.write_float_f64_taint(*self) }
1664 }
1665 /// Validation is deferred into the per-element taint write —
1666 /// the pre-scan pass was costing ~20% of total cycles per
1667 /// `perf record -c cycles` (the SIMD pand/pcmpeqd loop). The
1668 /// merged taint path costs only ~3 extra instructions per
1669 /// element (bit-mask check + cmov substitute + OR accumulate)
1670 /// and removes the separate pass entirely.
1671 #[inline]
1672 fn pre_validate_slice(_slice: &[Self]) -> Result<(), Error> {
1673 Ok(())
1674 }
1675 }
1676
1677 /// `f32` widens losslessly to `f64` for serialization. The decoded
1678 /// form on the parse side narrows via `as f32`, mirroring this.
1679 impl ToJson for f32 {
1680 const MIN_SERIALIZED_LEN: usize = 1;
1681 const MAX_SERIALIZED_LEN: usize = 32;
1682 // The taint path handles validation per-element now — no pre-scan.
1683 const NEEDS_VALIDATION: bool = false;
1684 #[inline]
1685 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1686 w.write_float_f64(f64::from(*self))
1687 }
1688 /// SAFETY: caller has reserved `MAX_SERIALIZED_LEN` bytes via
1689 /// `reserve_hint`. Non-finite inputs are tolerated via the
1690 /// sink's taint accumulator.
1691 #[inline]
1692 #[allow(unsafe_code)]
1693 unsafe fn write_json_in_reserved<W: JsonWrite + ?Sized>(
1694 &self,
1695 w: &mut W,
1696 ) -> Result<(), W::Error> {
1697 // SAFETY: forwarded to the sink's taint-tracking float path.
1698 unsafe { w.write_float_f64_taint(f64::from(*self)) }
1699 }
1700 }
1701
1702 /// JSON has no `char` primitive — encode as a one-character string,
1703 /// matching the `FromJson` direction.
1704 impl ToJson for char {
1705 const MIN_SERIALIZED_LEN: usize = 3; // "\"x\""
1706 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1707 let mut buf = [0u8; 4];
1708 let s: &str = self.encode_utf8(&mut buf);
1709 w.write_escaped_str(s)
1710 }
1711 }
1712
1713 impl<T: ToJson + ?Sized> ToJson for Box<T> {
1714 const MIN_SERIALIZED_LEN: usize = T::MIN_SERIALIZED_LEN;
1715 #[inline]
1716 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1717 (**self).write_json(w)
1718 }
1719 }
1720
1721 impl<T: ToJson + ?Sized> ToJson for Rc<T> {
1722 const MIN_SERIALIZED_LEN: usize = T::MIN_SERIALIZED_LEN;
1723 #[inline]
1724 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1725 (**self).write_json(w)
1726 }
1727 }
1728
1729 impl<T: ToJson + ?Sized> ToJson for Arc<T> {
1730 const MIN_SERIALIZED_LEN: usize = T::MIN_SERIALIZED_LEN;
1731 #[inline]
1732 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1733 (**self).write_json(w)
1734 }
1735 }
1736
1737 impl<T: ToJson> ToJson for alloc::vec::Vec<T> {
1738 const MIN_SERIALIZED_LEN: usize = 2; // "[]"
1739 #[inline]
1740 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1741 // Forward to the slice impl in the parent module.
1742 self.as_slice().write_json(w)
1743 }
1744 }
1745
1746 // -----------------------------------------------------------------
1747 // Map and set collections.
1748 //
1749 // Mirror image of `de.rs`: keys are written via `MapKeyOut`, which
1750 // exposes the canonical `&str` form regardless of whether the
1751 // underlying type is `String`, `&str`, or `Cow<'_, str>`. Values
1752 // serialize via their own `ToJson` impl. Sets serialize as arrays
1753 // in iteration order — for `HashSet` that's hash-bucket order,
1754 // matching the convention serde_json uses.
1755 // -----------------------------------------------------------------
1756
1757 /// Sealed adapter from a map's key type to the borrowed `&str` form.
1758 ///
1759 /// JSON object keys are always strings, so any map serialized via
1760 /// `ToJson` needs its key type to expose a `&str` view. Implemented
1761 /// for `String`, `&str`, and `Cow<'_, str>` — the same set the
1762 /// parse side supports via `MapKey`.
1763 pub trait MapKeyOut {
1764 fn as_str(&self) -> &str;
1765 }
1766
1767 impl MapKeyOut for String {
1768 #[inline]
1769 fn as_str(&self) -> &str {
1770 self
1771 }
1772 }
1773
1774 impl MapKeyOut for &str {
1775 #[inline]
1776 fn as_str(&self) -> &str {
1777 self
1778 }
1779 }
1780
1781 impl MapKeyOut for Cow<'_, str> {
1782 #[inline]
1783 fn as_str(&self) -> &str {
1784 self.as_ref()
1785 }
1786 }
1787
1788 /// Shared object-writing helper. Mirrors `write_array` for arrays.
1789 /// Pulled out so `BTreeMap` and `HashMap` (and any future map type)
1790 /// cannot drift on punctuation or empty-object handling.
1791 fn write_object<'a, K, V, I, W>(iter: I, w: &mut W) -> Result<(), W::Error>
1792 where
1793 K: MapKeyOut + 'a,
1794 V: ToJson + 'a,
1795 I: IntoIterator<Item = (&'a K, &'a V)>,
1796 W: JsonWrite + ?Sized,
1797 {
1798 w.write_byte(b'{')?;
1799 let mut first = true;
1800 for (k, v) in iter {
1801 if !first {
1802 w.write_byte(b',')?;
1803 }
1804 w.write_escaped_str(k.as_str())?;
1805 w.write_byte(b':')?;
1806 v.write_json(w)?;
1807 first = false;
1808 }
1809 w.write_byte(b'}')
1810 }
1811
1812 impl<K: MapKeyOut, V: ToJson> ToJson for alloc::collections::BTreeMap<K, V> {
1813 #[inline]
1814 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1815 write_object(self.iter(), w)
1816 }
1817 }
1818
1819 impl<T: ToJson> ToJson for alloc::collections::BTreeSet<T> {
1820 #[inline]
1821 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1822 super::write_array(self.iter(), w)
1823 }
1824 }
1825
1826 #[cfg(feature = "std")]
1827 impl<K, V, S> ToJson for std::collections::HashMap<K, V, S>
1828 where
1829 K: MapKeyOut + ::core::hash::Hash + Eq,
1830 V: ToJson,
1831 S: ::core::hash::BuildHasher,
1832 {
1833 #[inline]
1834 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1835 write_object(self.iter(), w)
1836 }
1837 }
1838
1839 #[cfg(feature = "std")]
1840 impl<T, S> ToJson for std::collections::HashSet<T, S>
1841 where
1842 T: ToJson + ::core::hash::Hash + Eq,
1843 S: ::core::hash::BuildHasher,
1844 {
1845 #[inline]
1846 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1847 super::write_array(self.iter(), w)
1848 }
1849 }
1850
1851 /// Encode `SystemTime` as fractional seconds since `UNIX_EPOCH`.
1852 /// Times before the epoch serialize as negative numbers; the
1853 /// parse side accepts the same shape.
1854 #[cfg(feature = "std")]
1855 impl ToJson for std::time::SystemTime {
1856 #[inline]
1857 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1858 // `duration_since(UNIX_EPOCH)` returns Err with the negated
1859 // duration when self < UNIX_EPOCH. Encode the sign back.
1860 let secs = match self.duration_since(std::time::UNIX_EPOCH) {
1861 Ok(d) => d.as_secs_f64(),
1862 Err(e) => -e.duration().as_secs_f64(),
1863 };
1864 w.write_float_f64(secs)
1865 }
1866 }
1867
1868 /// Encode `Duration` as fractional seconds, mirroring the parse-side
1869 /// `from_secs_f64` adapter. Negative durations are unrepresentable
1870 /// (`Duration` is unsigned), and the float impl already rejects
1871 /// non-finite output, so this never errors for valid inputs.
1872 #[cfg(feature = "std")]
1873 impl ToJson for std::time::Duration {
1874 #[inline]
1875 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1876 w.write_float_f64(self.as_secs_f64())
1877 }
1878 }
1879
1880 // -----------------------------------------------------------------
1881 // std::net and std::path adapters.
1882 //
1883 // Each writes its canonical Display form as a quoted JSON string,
1884 // matching the FromJson `parse_from_str` adapter on the parse side.
1885 // -----------------------------------------------------------------
1886
1887 /// Display the value into a small scratch buffer, then write it as
1888 /// a JSON string. The `fmt::Write` trait fills our scratch `String`;
1889 /// from there we reuse `write_escaped_str` even though these types'
1890 /// canonical text never contains characters that need escaping —
1891 /// the cost is one SIMD scan that exits immediately, and using the
1892 /// escape path keeps a single string-writing entry point.
1893 #[cfg(feature = "std")]
1894 fn write_display<T: ::core::fmt::Display, W: JsonWrite + ?Sized>(
1895 v: &T,
1896 w: &mut W,
1897 ) -> Result<(), W::Error> {
1898 use ::core::fmt::Write as _;
1899 let mut buf = String::new();
1900 // fmt::Write into a String is infallible.
1901 let _ = write!(&mut buf, "{v}");
1902 w.write_escaped_str(&buf)
1903 }
1904
1905 #[cfg(feature = "std")]
1906 impl ToJson for std::net::IpAddr {
1907 #[inline]
1908 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1909 write_display(self, w)
1910 }
1911 }
1912
1913 #[cfg(feature = "std")]
1914 impl ToJson for std::net::Ipv4Addr {
1915 #[inline]
1916 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1917 write_display(self, w)
1918 }
1919 }
1920
1921 #[cfg(feature = "std")]
1922 impl ToJson for std::net::Ipv6Addr {
1923 #[inline]
1924 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1925 write_display(self, w)
1926 }
1927 }
1928
1929 #[cfg(feature = "std")]
1930 impl ToJson for std::net::SocketAddr {
1931 #[inline]
1932 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1933 write_display(self, w)
1934 }
1935 }
1936
1937 /// `PathBuf` round-trips only for paths whose bytes are valid UTF-8.
1938 /// `Path::display()` lossily replaces invalid bytes — we want a
1939 /// clean error in that case, but JSON has no lossless path encoding
1940 /// anyway, so the convention matches the parse side: assume UTF-8.
1941 /// `to_string_lossy` here is the symmetric move.
1942 #[cfg(feature = "std")]
1943 impl ToJson for std::path::PathBuf {
1944 #[inline]
1945 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1946 w.write_escaped_str(&self.to_string_lossy())
1947 }
1948 }
1949
1950 #[cfg(feature = "std")]
1951 impl ToJson for std::path::Path {
1952 #[inline]
1953 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1954 w.write_escaped_str(&self.to_string_lossy())
1955 }
1956 }
1957
1958 // -----------------------------------------------------------------
1959 // IndexMap / IndexSet (optional `indexmap` feature).
1960 //
1961 // Insertion-order iteration is the differentiator from
1962 // HashMap/BTreeMap; the wire shape is the same.
1963 // -----------------------------------------------------------------
1964
1965 #[cfg(feature = "indexmap")]
1966 impl<K, V, S> ToJson for indexmap::IndexMap<K, V, S>
1967 where
1968 K: super::MapKeyOut + ::core::hash::Hash + Eq,
1969 V: ToJson,
1970 S: ::core::hash::BuildHasher,
1971 {
1972 #[inline]
1973 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1974 write_object(self.iter(), w)
1975 }
1976 }
1977
1978 #[cfg(feature = "indexmap")]
1979 impl<T, S> ToJson for indexmap::IndexSet<T, S>
1980 where
1981 T: ToJson + ::core::hash::Hash + Eq,
1982 S: ::core::hash::BuildHasher,
1983 {
1984 #[inline]
1985 fn write_json<W: JsonWrite + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> {
1986 super::write_array(self.iter(), w)
1987 }
1988 }
1989}
1990
1991#[cfg(feature = "alloc")]
1992pub use alloc_impls::MapKeyOut;
1993
1994// Keep `Position` / `ErrorKind` imports live so PR 2's float path can lean
1995// on them without re-importing. The float impls return
1996// `Error::new(ErrorKind::NonFiniteFloat, Position::START)` since serializer
1997// errors don't have an input-byte position to point at.
1998#[allow(dead_code)]
1999const _: fn() -> Error = || Error::new(ErrorKind::NonFiniteFloat, Position::START);