Skip to main content

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