facet_json/
serializer.rs

1extern crate alloc;
2
3use alloc::{string::String, vec::Vec};
4
5use facet_core::Facet;
6use facet_format::{FormatSerializer, ScalarValue, SerializeError, serialize_root};
7use facet_reflect::Peek;
8
9/// Options for JSON serialization.
10#[derive(Debug, Clone)]
11pub struct SerializeOptions {
12    /// Whether to pretty-print with indentation (default: false)
13    pub pretty: bool,
14    /// Indentation string for pretty-printing (default: "  ")
15    pub indent: &'static str,
16}
17
18impl Default for SerializeOptions {
19    fn default() -> Self {
20        Self {
21            pretty: false,
22            indent: "  ",
23        }
24    }
25}
26
27impl SerializeOptions {
28    /// Create new default options (compact output).
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Enable pretty-printing with default indentation.
34    pub fn pretty(mut self) -> Self {
35        self.pretty = true;
36        self
37    }
38
39    /// Set a custom indentation string (implies pretty-printing).
40    pub fn indent(mut self, indent: &'static str) -> Self {
41        self.indent = indent;
42        self.pretty = true;
43        self
44    }
45}
46
47#[derive(Debug)]
48pub struct JsonSerializeError {
49    msg: &'static str,
50}
51
52impl core::fmt::Display for JsonSerializeError {
53    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54        f.write_str(self.msg)
55    }
56}
57
58impl std::error::Error for JsonSerializeError {}
59
60#[derive(Debug, Clone, Copy)]
61enum Ctx {
62    Struct { first: bool },
63    Seq { first: bool },
64}
65
66/// JSON serializer with configurable formatting options.
67pub struct JsonSerializer {
68    out: Vec<u8>,
69    stack: Vec<Ctx>,
70    options: SerializeOptions,
71}
72
73impl JsonSerializer {
74    /// Create a new JSON serializer with default (compact) options.
75    pub fn new() -> Self {
76        Self::with_options(SerializeOptions::default())
77    }
78
79    /// Create a new JSON serializer with the given options.
80    pub fn with_options(options: SerializeOptions) -> Self {
81        Self {
82            out: Vec::new(),
83            stack: Vec::new(),
84            options,
85        }
86    }
87
88    /// Consume the serializer and return the output bytes.
89    pub fn finish(self) -> Vec<u8> {
90        self.out
91    }
92
93    /// Current nesting depth (for indentation).
94    fn depth(&self) -> usize {
95        self.stack.len()
96    }
97
98    /// Write a newline and indentation if in pretty mode.
99    fn write_indent(&mut self) {
100        if self.options.pretty {
101            self.out.push(b'\n');
102            for _ in 0..self.depth() {
103                self.out.extend_from_slice(self.options.indent.as_bytes());
104            }
105        }
106    }
107
108    fn before_value(&mut self) -> Result<(), JsonSerializeError> {
109        match self.stack.last_mut() {
110            Some(Ctx::Seq { first }) => {
111                if !*first {
112                    self.out.push(b',');
113                }
114                *first = false;
115                self.write_indent();
116            }
117            Some(Ctx::Struct { .. }) => {
118                // struct values are separated by `field_key`
119            }
120            None => {}
121        }
122        Ok(())
123    }
124
125    /// Optimized JSON string writing with SIMD-like 16-byte fast path.
126    ///
127    /// For ASCII strings without special characters, processes 16 bytes at a time.
128    /// Falls back to character-by-character escaping when needed.
129    fn write_json_string(&mut self, s: &str) {
130        const STEP_SIZE: usize = 16; // u128 = 16 bytes
131        type Chunk = [u8; STEP_SIZE];
132
133        self.out.push(b'"');
134
135        let mut s = s;
136        while let Some(Ok(chunk)) = s.as_bytes().get(..STEP_SIZE).map(Chunk::try_from) {
137            let window = u128::from_ne_bytes(chunk);
138            // Check all 16 bytes at once:
139            // 1. All ASCII (high bit clear): window & 0x80...80 == 0
140            // 2. No quotes (0x22): !contains_byte(window, 0x22)
141            // 3. No backslashes (0x5c): !contains_byte(window, 0x5c)
142            // 4. No control chars (< 0x20): top 3 bits set for all bytes
143            let completely_ascii = window & 0x80808080808080808080808080808080 == 0;
144            let quote_free = !contains_byte(window, 0x22);
145            let backslash_free = !contains_byte(window, 0x5c);
146            let control_char_free = no_control_chars(window);
147
148            if completely_ascii && quote_free && backslash_free && control_char_free {
149                // Fast path: copy 16 bytes directly
150                self.out.extend_from_slice(&chunk);
151                s = &s[STEP_SIZE..];
152            } else {
153                // Slow path: escape character by character for this chunk
154                let mut chars = s.chars();
155                let mut count = STEP_SIZE;
156                for c in &mut chars {
157                    self.write_json_escaped_char(c);
158                    count = count.saturating_sub(c.len_utf8());
159                    if count == 0 {
160                        break;
161                    }
162                }
163                s = chars.as_str();
164            }
165        }
166
167        // Handle remaining bytes (< 16)
168        for c in s.chars() {
169            self.write_json_escaped_char(c);
170        }
171
172        self.out.push(b'"');
173    }
174
175    #[inline]
176    fn write_json_escaped_char(&mut self, c: char) {
177        match c {
178            '"' => self.out.extend_from_slice(b"\\\""),
179            '\\' => self.out.extend_from_slice(b"\\\\"),
180            '\n' => self.out.extend_from_slice(b"\\n"),
181            '\r' => self.out.extend_from_slice(b"\\r"),
182            '\t' => self.out.extend_from_slice(b"\\t"),
183            '\u{08}' => self.out.extend_from_slice(b"\\b"),
184            '\u{0C}' => self.out.extend_from_slice(b"\\f"),
185            c if c.is_ascii_control() => {
186                let code_point = c as u32;
187                let to_hex = |d: u32| {
188                    if d < 10 {
189                        b'0' + d as u8
190                    } else {
191                        b'a' + (d - 10) as u8
192                    }
193                };
194                let buf = [
195                    b'\\',
196                    b'u',
197                    to_hex((code_point >> 12) & 0xF),
198                    to_hex((code_point >> 8) & 0xF),
199                    to_hex((code_point >> 4) & 0xF),
200                    to_hex(code_point & 0xF),
201                ];
202                self.out.extend_from_slice(&buf);
203            }
204            c if c.is_ascii() => {
205                self.out.push(c as u8);
206            }
207            c => {
208                let mut buf = [0u8; 4];
209                let len = c.encode_utf8(&mut buf).len();
210                self.out.extend_from_slice(&buf[..len]);
211            }
212        }
213    }
214}
215
216/// Check if any byte in the u128 equals the target byte.
217/// Uses the SWAR (SIMD Within A Register) technique.
218#[inline]
219fn contains_byte(val: u128, byte: u8) -> bool {
220    let mask = 0x01010101010101010101010101010101u128 * (byte as u128);
221    let xor_result = val ^ mask;
222    let has_zero = (xor_result.wrapping_sub(0x01010101010101010101010101010101))
223        & !xor_result
224        & 0x80808080808080808080808080808080;
225    has_zero != 0
226}
227
228/// Check that all bytes have at least one of the top 3 bits set (i.e., >= 0x20).
229/// This means no control characters (0x00-0x1F).
230#[inline]
231fn no_control_chars(value: u128) -> bool {
232    let masked = value & 0xe0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0;
233    let has_zero = (masked.wrapping_sub(0x01010101010101010101010101010101))
234        & !masked
235        & 0x80808080808080808080808080808080;
236    has_zero == 0
237}
238
239impl Default for JsonSerializer {
240    fn default() -> Self {
241        Self::new()
242    }
243}
244
245impl FormatSerializer for JsonSerializer {
246    type Error = JsonSerializeError;
247
248    fn begin_struct(&mut self) -> Result<(), Self::Error> {
249        self.before_value()?;
250        self.out.push(b'{');
251        self.stack.push(Ctx::Struct { first: true });
252        Ok(())
253    }
254
255    fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
256        match self.stack.last_mut() {
257            Some(Ctx::Struct { first }) => {
258                if !*first {
259                    self.out.push(b',');
260                }
261                *first = false;
262                self.write_indent();
263                self.write_json_string(key);
264                self.out.push(b':');
265                if self.options.pretty {
266                    self.out.push(b' ');
267                }
268                Ok(())
269            }
270            _ => Err(JsonSerializeError {
271                msg: "field_key called outside of a struct",
272            }),
273        }
274    }
275
276    fn end_struct(&mut self) -> Result<(), Self::Error> {
277        match self.stack.pop() {
278            Some(Ctx::Struct { first }) => {
279                // Only add newline/indent before closing brace if struct was non-empty
280                if !first {
281                    self.write_indent();
282                }
283                self.out.push(b'}');
284                Ok(())
285            }
286            _ => Err(JsonSerializeError {
287                msg: "end_struct called without matching begin_struct",
288            }),
289        }
290    }
291
292    fn begin_seq(&mut self) -> Result<(), Self::Error> {
293        self.before_value()?;
294        self.out.push(b'[');
295        self.stack.push(Ctx::Seq { first: true });
296        Ok(())
297    }
298
299    fn end_seq(&mut self) -> Result<(), Self::Error> {
300        match self.stack.pop() {
301            Some(Ctx::Seq { first }) => {
302                // Only add newline/indent before closing bracket if seq was non-empty
303                if !first {
304                    self.write_indent();
305                }
306                self.out.push(b']');
307                Ok(())
308            }
309            _ => Err(JsonSerializeError {
310                msg: "end_seq called without matching begin_seq",
311            }),
312        }
313    }
314
315    fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
316        self.before_value()?;
317        match scalar {
318            ScalarValue::Null => self.out.extend_from_slice(b"null"),
319            ScalarValue::Bool(v) => {
320                if v {
321                    self.out.extend_from_slice(b"true")
322                } else {
323                    self.out.extend_from_slice(b"false")
324                }
325            }
326            ScalarValue::Char(c) => {
327                self.out.push(b'"');
328                self.write_json_escaped_char(c);
329                self.out.push(b'"');
330            }
331            ScalarValue::I64(v) => {
332                #[cfg(feature = "fast")]
333                self.out
334                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
335                #[cfg(not(feature = "fast"))]
336                self.out.extend_from_slice(v.to_string().as_bytes());
337            }
338            ScalarValue::U64(v) => {
339                #[cfg(feature = "fast")]
340                self.out
341                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
342                #[cfg(not(feature = "fast"))]
343                self.out.extend_from_slice(v.to_string().as_bytes());
344            }
345            ScalarValue::I128(v) => {
346                #[cfg(feature = "fast")]
347                self.out
348                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
349                #[cfg(not(feature = "fast"))]
350                self.out.extend_from_slice(v.to_string().as_bytes());
351            }
352            ScalarValue::U128(v) => {
353                #[cfg(feature = "fast")]
354                self.out
355                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
356                #[cfg(not(feature = "fast"))]
357                self.out.extend_from_slice(v.to_string().as_bytes());
358            }
359            ScalarValue::F64(v) => {
360                #[cfg(feature = "fast")]
361                self.out
362                    .extend_from_slice(zmij::Buffer::new().format(v).as_bytes());
363                #[cfg(not(feature = "fast"))]
364                self.out.extend_from_slice(v.to_string().as_bytes());
365            }
366            ScalarValue::Str(s) | ScalarValue::StringlyTyped(s) => self.write_json_string(&s),
367            ScalarValue::Bytes(_) => {
368                return Err(JsonSerializeError {
369                    msg: "bytes serialization unsupported for json",
370                });
371            }
372        }
373        Ok(())
374    }
375
376    fn raw_serialize_shape(&self) -> Option<&'static facet_core::Shape> {
377        Some(crate::RawJson::SHAPE)
378    }
379
380    fn raw_scalar(&mut self, content: &str) -> Result<(), Self::Error> {
381        // For RawJson, output the content directly without escaping
382        self.before_value()?;
383        self.out.extend_from_slice(content.as_bytes());
384        Ok(())
385    }
386}
387
388/// Serialize a value to JSON bytes.
389///
390/// # Example
391///
392/// ```
393/// use facet::Facet;
394/// use facet_json::to_vec;
395///
396/// #[derive(Facet)]
397/// struct Point { x: i32, y: i32 }
398///
399/// let point = Point { x: 10, y: 20 };
400/// let bytes = to_vec(&point).unwrap();
401/// assert_eq!(bytes, br#"{"x":10,"y":20}"#);
402/// ```
403pub fn to_vec<'facet, T>(value: &'_ T) -> Result<Vec<u8>, SerializeError<JsonSerializeError>>
404where
405    T: Facet<'facet> + ?Sized,
406{
407    to_vec_with_options(value, &SerializeOptions::default())
408}
409
410/// Serialize a value to pretty-printed JSON bytes.
411///
412/// # Example
413///
414/// ```
415/// use facet::Facet;
416/// use facet_json::to_vec_pretty;
417///
418/// #[derive(Facet)]
419/// struct Point { x: i32, y: i32 }
420///
421/// let point = Point { x: 10, y: 20 };
422/// let bytes = to_vec_pretty(&point).unwrap();
423/// assert!(bytes.contains(&b'\n'));
424/// ```
425pub fn to_vec_pretty<'facet, T>(value: &'_ T) -> Result<Vec<u8>, SerializeError<JsonSerializeError>>
426where
427    T: Facet<'facet> + ?Sized,
428{
429    to_vec_with_options(value, &SerializeOptions::default().pretty())
430}
431
432/// Serialize a value to JSON bytes with custom options.
433///
434/// # Example
435///
436/// ```
437/// use facet::Facet;
438/// use facet_json::{to_vec_with_options, SerializeOptions};
439///
440/// #[derive(Facet)]
441/// struct Point { x: i32, y: i32 }
442///
443/// let point = Point { x: 10, y: 20 };
444///
445/// // Compact output
446/// let bytes = to_vec_with_options(&point, &SerializeOptions::default()).unwrap();
447/// assert_eq!(bytes, br#"{"x":10,"y":20}"#);
448///
449/// // Pretty output with tabs
450/// let bytes = to_vec_with_options(&point, &SerializeOptions::default().indent("\t")).unwrap();
451/// assert!(bytes.contains(&b'\n'));
452/// ```
453pub fn to_vec_with_options<'facet, T>(
454    value: &'_ T,
455    options: &SerializeOptions,
456) -> Result<Vec<u8>, SerializeError<JsonSerializeError>>
457where
458    T: Facet<'facet> + ?Sized,
459{
460    let mut serializer = JsonSerializer::with_options(options.clone());
461    serialize_root(&mut serializer, Peek::new(value))?;
462    Ok(serializer.finish())
463}
464
465/// Serialize a value to a JSON string.
466///
467/// # Example
468///
469/// ```
470/// use facet::Facet;
471/// use facet_json::to_string;
472///
473/// #[derive(Facet)]
474/// struct Person { name: String, age: u32 }
475///
476/// let person = Person { name: "Alice".into(), age: 30 };
477/// let json = to_string(&person).unwrap();
478/// assert_eq!(json, r#"{"name":"Alice","age":30}"#);
479/// ```
480pub fn to_string<'facet, T>(value: &'_ T) -> Result<String, SerializeError<JsonSerializeError>>
481where
482    T: Facet<'facet> + ?Sized,
483{
484    let bytes = to_vec(value)?;
485    // JSON output is always valid UTF-8, so this unwrap is safe
486    Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
487}
488
489/// Serialize a value to a pretty-printed JSON string.
490///
491/// # Example
492///
493/// ```
494/// use facet::Facet;
495/// use facet_json::to_string_pretty;
496///
497/// #[derive(Facet)]
498/// struct Person { name: String, age: u32 }
499///
500/// let person = Person { name: "Alice".into(), age: 30 };
501/// let json = to_string_pretty(&person).unwrap();
502/// assert!(json.contains('\n'));
503/// ```
504pub fn to_string_pretty<'facet, T>(
505    value: &'_ T,
506) -> Result<String, SerializeError<JsonSerializeError>>
507where
508    T: Facet<'facet> + ?Sized,
509{
510    let bytes = to_vec_pretty(value)?;
511    Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
512}
513
514/// Serialize a value to a JSON string with custom options.
515///
516/// # Example
517///
518/// ```
519/// use facet::Facet;
520/// use facet_json::{to_string_with_options, SerializeOptions};
521///
522/// #[derive(Facet)]
523/// struct Person { name: String, age: u32 }
524///
525/// let person = Person { name: "Alice".into(), age: 30 };
526///
527/// // Compact output
528/// let json = to_string_with_options(&person, &SerializeOptions::default()).unwrap();
529/// assert_eq!(json, r#"{"name":"Alice","age":30}"#);
530///
531/// // Pretty output with tabs
532/// let json = to_string_with_options(&person, &SerializeOptions::default().indent("\t")).unwrap();
533/// assert!(json.contains('\n'));
534/// ```
535pub fn to_string_with_options<'facet, T>(
536    value: &'_ T,
537    options: &SerializeOptions,
538) -> Result<String, SerializeError<JsonSerializeError>>
539where
540    T: Facet<'facet> + ?Sized,
541{
542    let bytes = to_vec_with_options(value, options)?;
543    Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
544}
545
546// ── Peek-based serialization ──
547
548/// Serialize a `Peek` instance to a JSON string.
549///
550/// This allows serializing values without requiring ownership, useful when
551/// you already have a `Peek` from reflection operations.
552///
553/// # Example
554///
555/// ```
556/// use facet::Facet;
557/// use facet_reflect::Peek;
558/// use facet_json::peek_to_string;
559///
560/// #[derive(Facet)]
561/// struct Point { x: i32, y: i32 }
562///
563/// let point = Point { x: 10, y: 20 };
564/// let json = peek_to_string(Peek::new(&point)).unwrap();
565/// assert_eq!(json, r#"{"x":10,"y":20}"#);
566/// ```
567pub fn peek_to_string<'input, 'facet>(
568    peek: Peek<'input, 'facet>,
569) -> Result<String, SerializeError<JsonSerializeError>> {
570    peek_to_string_with_options(peek, &SerializeOptions::default())
571}
572
573/// Serialize a `Peek` instance to a pretty-printed JSON string.
574///
575/// # Example
576///
577/// ```
578/// use facet::Facet;
579/// use facet_reflect::Peek;
580/// use facet_json::peek_to_string_pretty;
581///
582/// #[derive(Facet)]
583/// struct Point { x: i32, y: i32 }
584///
585/// let point = Point { x: 10, y: 20 };
586/// let json = peek_to_string_pretty(Peek::new(&point)).unwrap();
587/// assert!(json.contains('\n'));
588/// ```
589pub fn peek_to_string_pretty<'input, 'facet>(
590    peek: Peek<'input, 'facet>,
591) -> Result<String, SerializeError<JsonSerializeError>> {
592    peek_to_string_with_options(peek, &SerializeOptions::default().pretty())
593}
594
595/// Serialize a `Peek` instance to a JSON string with custom options.
596///
597/// # Example
598///
599/// ```
600/// use facet::Facet;
601/// use facet_reflect::Peek;
602/// use facet_json::{peek_to_string_with_options, SerializeOptions};
603///
604/// #[derive(Facet)]
605/// struct Point { x: i32, y: i32 }
606///
607/// let point = Point { x: 10, y: 20 };
608/// let json = peek_to_string_with_options(
609///     Peek::new(&point),
610///     &SerializeOptions::default().indent("\t"),
611/// ).unwrap();
612/// assert!(json.contains('\n'));
613/// ```
614pub fn peek_to_string_with_options<'input, 'facet>(
615    peek: Peek<'input, 'facet>,
616    options: &SerializeOptions,
617) -> Result<String, SerializeError<JsonSerializeError>> {
618    let mut serializer = JsonSerializer::with_options(options.clone());
619    serialize_root(&mut serializer, peek)?;
620    let bytes = serializer.finish();
621    Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
622}
623
624// ── Writer-based serialization (std::io::Write) ──
625
626/// Serialize a value to JSON and write it to a `std::io::Write` writer.
627///
628/// # Example
629///
630/// ```
631/// use facet::Facet;
632/// use facet_json::to_writer_std;
633///
634/// #[derive(Facet)]
635/// struct Person {
636///     name: String,
637///     age: u32,
638/// }
639///
640/// let person = Person { name: "Alice".into(), age: 30 };
641/// let mut buffer = Vec::new();
642/// to_writer_std(&mut buffer, &person).unwrap();
643/// assert_eq!(buffer, br#"{"name":"Alice","age":30}"#);
644/// ```
645pub fn to_writer_std<'facet, W, T>(writer: W, value: &T) -> std::io::Result<()>
646where
647    W: std::io::Write,
648    T: Facet<'facet> + ?Sized,
649{
650    peek_to_writer_std(writer, Peek::new(value))
651}
652
653/// Serialize a value to pretty-printed JSON and write it to a `std::io::Write` writer.
654///
655/// # Example
656///
657/// ```
658/// use facet::Facet;
659/// use facet_json::to_writer_std_pretty;
660///
661/// #[derive(Facet)]
662/// struct Person {
663///     name: String,
664///     age: u32,
665/// }
666///
667/// let person = Person { name: "Alice".into(), age: 30 };
668/// let mut buffer = Vec::new();
669/// to_writer_std_pretty(&mut buffer, &person).unwrap();
670/// assert!(String::from_utf8_lossy(&buffer).contains('\n'));
671/// ```
672pub fn to_writer_std_pretty<'facet, W, T>(writer: W, value: &T) -> std::io::Result<()>
673where
674    W: std::io::Write,
675    T: Facet<'facet> + ?Sized,
676{
677    peek_to_writer_std_pretty(writer, Peek::new(value))
678}
679
680/// Serialize a value to JSON with custom options and write it to a `std::io::Write` writer.
681///
682/// # Example
683///
684/// ```
685/// use facet::Facet;
686/// use facet_json::{to_writer_std_with_options, SerializeOptions};
687///
688/// #[derive(Facet)]
689/// struct Person {
690///     name: String,
691///     age: u32,
692/// }
693///
694/// let person = Person { name: "Alice".into(), age: 30 };
695///
696/// // Compact output
697/// let mut buffer = Vec::new();
698/// to_writer_std_with_options(&mut buffer, &person, &SerializeOptions::default()).unwrap();
699/// assert_eq!(buffer, br#"{"name":"Alice","age":30}"#);
700///
701/// // Pretty output with tabs
702/// let mut buffer = Vec::new();
703/// to_writer_std_with_options(&mut buffer, &person, &SerializeOptions::default().indent("\t")).unwrap();
704/// assert!(String::from_utf8_lossy(&buffer).contains('\n'));
705/// ```
706pub fn to_writer_std_with_options<'facet, W, T>(
707    writer: W,
708    value: &T,
709    options: &SerializeOptions,
710) -> std::io::Result<()>
711where
712    W: std::io::Write,
713    T: Facet<'facet> + ?Sized,
714{
715    peek_to_writer_std_with_options(writer, Peek::new(value), options)
716}
717
718/// Serialize a `Peek` instance to JSON and write it to a `std::io::Write` writer.
719pub fn peek_to_writer_std<'input, 'facet, W>(
720    writer: W,
721    peek: Peek<'input, 'facet>,
722) -> std::io::Result<()>
723where
724    W: std::io::Write,
725{
726    peek_to_writer_std_with_options(writer, peek, &SerializeOptions::default())
727}
728
729/// Serialize a `Peek` instance to pretty-printed JSON and write it to a `std::io::Write` writer.
730pub fn peek_to_writer_std_pretty<'input, 'facet, W>(
731    writer: W,
732    peek: Peek<'input, 'facet>,
733) -> std::io::Result<()>
734where
735    W: std::io::Write,
736{
737    peek_to_writer_std_with_options(writer, peek, &SerializeOptions::default().pretty())
738}
739
740/// Serialize a `Peek` instance to JSON with custom options and write it to a `std::io::Write` writer.
741pub fn peek_to_writer_std_with_options<'input, 'facet, W>(
742    mut writer: W,
743    peek: Peek<'input, 'facet>,
744    options: &SerializeOptions,
745) -> std::io::Result<()>
746where
747    W: std::io::Write,
748{
749    // Serialize to bytes first, then write
750    // This is simpler and avoids the complexity of incremental writes
751    let mut serializer = JsonSerializer::with_options(options.clone());
752    serialize_root(&mut serializer, peek)
753        .map_err(|e| std::io::Error::other(alloc::format!("{:?}", e)))?;
754    writer.write_all(&serializer.finish())
755}