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    /// How byte sequences (`Vec<u8>`, `[u8; N]`, `bytes::Bytes`, etc.) are serialized.
19    pub bytes_format: BytesFormat,
20}
21
22impl Default for SerializeOptions {
23    fn default() -> Self {
24        Self {
25            pretty: false,
26            indent: "  ",
27            bytes_format: BytesFormat::default(),
28        }
29    }
30}
31
32/// Byte serialization format for JSON.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
34pub enum BytesFormat {
35    /// Serialize as a JSON array of numbers (e.g., `[0, 255, 42]`).
36    #[default]
37    Array,
38    /// Serialize as a JSON string containing hex bytes (e.g., `"0x00ff2a"`).
39    Hex(HexBytesOptions),
40}
41
42/// Options for hex byte serialization.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub struct HexBytesOptions {
45    /// Truncate when the byte length exceeds this threshold.
46    ///
47    /// `None` means never truncate.
48    pub truncate_above: Option<usize>,
49    /// Number of bytes to keep from the start when truncating.
50    pub head: usize,
51    /// Number of bytes to keep from the end when truncating.
52    pub tail: usize,
53}
54
55impl Default for HexBytesOptions {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl HexBytesOptions {
62    /// Create default hex byte options (no truncation).
63    pub const fn new() -> Self {
64        Self {
65            truncate_above: None,
66            head: 32,
67            tail: 32,
68        }
69    }
70
71    /// Truncate when byte length exceeds `truncate_above`.
72    pub const fn truncate(mut self, truncate_above: usize) -> Self {
73        self.truncate_above = Some(truncate_above);
74        self
75    }
76
77    /// Set the number of head/tail bytes to keep when truncating.
78    pub const fn head_tail(mut self, head: usize, tail: usize) -> Self {
79        self.head = head;
80        self.tail = tail;
81        self
82    }
83}
84
85impl SerializeOptions {
86    /// Create new default options (compact output).
87    pub fn new() -> Self {
88        Self::default()
89    }
90
91    /// Enable pretty-printing with default indentation.
92    pub const fn pretty(mut self) -> Self {
93        self.pretty = true;
94        self
95    }
96
97    /// Set a custom indentation string (implies pretty-printing).
98    pub const fn indent(mut self, indent: &'static str) -> Self {
99        self.indent = indent;
100        self.pretty = true;
101        self
102    }
103
104    /// Configure how byte sequences are serialized.
105    pub const fn bytes_format(mut self, bytes_format: BytesFormat) -> Self {
106        self.bytes_format = bytes_format;
107        self
108    }
109
110    /// Serialize byte sequences as JSON arrays of numbers.
111    pub const fn bytes_as_array(mut self) -> Self {
112        self.bytes_format = BytesFormat::Array;
113        self
114    }
115
116    /// Serialize byte sequences as hex strings without truncation.
117    pub const fn bytes_as_hex(mut self) -> Self {
118        self.bytes_format = BytesFormat::Hex(HexBytesOptions::new());
119        self
120    }
121
122    /// Serialize byte sequences as hex strings with custom options.
123    pub const fn bytes_as_hex_with_options(mut self, options: HexBytesOptions) -> Self {
124        self.bytes_format = BytesFormat::Hex(options);
125        self
126    }
127}
128
129#[derive(Debug)]
130pub struct JsonSerializeError {
131    msg: &'static str,
132}
133
134impl core::fmt::Display for JsonSerializeError {
135    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136        f.write_str(self.msg)
137    }
138}
139
140impl std::error::Error for JsonSerializeError {}
141
142#[derive(Debug, Clone, Copy)]
143enum Ctx {
144    Struct { first: bool },
145    Seq { first: bool },
146}
147
148/// JSON serializer with configurable formatting options.
149pub struct JsonSerializer {
150    out: Vec<u8>,
151    stack: Vec<Ctx>,
152    options: SerializeOptions,
153}
154
155impl JsonSerializer {
156    /// Create a new JSON serializer with default (compact) options.
157    pub fn new() -> Self {
158        Self::with_options(SerializeOptions::default())
159    }
160
161    /// Create a new JSON serializer with the given options.
162    pub const fn with_options(options: SerializeOptions) -> Self {
163        Self {
164            out: Vec::new(),
165            stack: Vec::new(),
166            options,
167        }
168    }
169
170    /// Consume the serializer and return the output bytes.
171    pub fn finish(self) -> Vec<u8> {
172        self.out
173    }
174
175    /// Current nesting depth (for indentation).
176    const fn depth(&self) -> usize {
177        self.stack.len()
178    }
179
180    /// Write a newline and indentation if in pretty mode.
181    fn write_indent(&mut self) {
182        if self.options.pretty {
183            self.out.push(b'\n');
184            for _ in 0..self.depth() {
185                self.out.extend_from_slice(self.options.indent.as_bytes());
186            }
187        }
188    }
189
190    fn before_value(&mut self) -> Result<(), JsonSerializeError> {
191        match self.stack.last_mut() {
192            Some(Ctx::Seq { first }) => {
193                if !*first {
194                    self.out.push(b',');
195                }
196                *first = false;
197                self.write_indent();
198            }
199            Some(Ctx::Struct { .. }) => {
200                // struct values are separated by `field_key`
201            }
202            None => {}
203        }
204        Ok(())
205    }
206
207    /// Optimized JSON string writing with SIMD-like 16-byte fast path.
208    ///
209    /// For ASCII strings without special characters, processes 16 bytes at a time.
210    /// Falls back to character-by-character escaping when needed.
211    fn write_json_string(&mut self, s: &str) {
212        const STEP_SIZE: usize = 16; // u128 = 16 bytes
213        type Chunk = [u8; STEP_SIZE];
214
215        self.out.push(b'"');
216
217        let mut s = s;
218        while let Some(Ok(chunk)) = s.as_bytes().get(..STEP_SIZE).map(Chunk::try_from) {
219            let window = u128::from_ne_bytes(chunk);
220            // Check all 16 bytes at once:
221            // 1. All ASCII (high bit clear): window & 0x80...80 == 0
222            // 2. No quotes (0x22): !contains_byte(window, 0x22)
223            // 3. No backslashes (0x5c): !contains_byte(window, 0x5c)
224            // 4. No control chars (< 0x20): top 3 bits set for all bytes
225            let completely_ascii = window & 0x80808080808080808080808080808080 == 0;
226            let quote_free = !contains_byte(window, 0x22);
227            let backslash_free = !contains_byte(window, 0x5c);
228            let control_char_free = no_control_chars(window);
229
230            if completely_ascii && quote_free && backslash_free && control_char_free {
231                // Fast path: copy 16 bytes directly
232                self.out.extend_from_slice(&chunk);
233                s = &s[STEP_SIZE..];
234            } else {
235                // Slow path: escape character by character for this chunk
236                let mut chars = s.chars();
237                let mut count = STEP_SIZE;
238                for c in &mut chars {
239                    self.write_json_escaped_char(c);
240                    count = count.saturating_sub(c.len_utf8());
241                    if count == 0 {
242                        break;
243                    }
244                }
245                s = chars.as_str();
246            }
247        }
248
249        // Handle remaining bytes (< 16)
250        for c in s.chars() {
251            self.write_json_escaped_char(c);
252        }
253
254        self.out.push(b'"');
255    }
256
257    #[inline]
258    fn write_json_escaped_char(&mut self, c: char) {
259        match c {
260            '"' => self.out.extend_from_slice(b"\\\""),
261            '\\' => self.out.extend_from_slice(b"\\\\"),
262            '\n' => self.out.extend_from_slice(b"\\n"),
263            '\r' => self.out.extend_from_slice(b"\\r"),
264            '\t' => self.out.extend_from_slice(b"\\t"),
265            '\u{08}' => self.out.extend_from_slice(b"\\b"),
266            '\u{0C}' => self.out.extend_from_slice(b"\\f"),
267            c if c.is_ascii_control() => {
268                let code_point = c as u32;
269                let to_hex = |d: u32| {
270                    if d < 10 {
271                        b'0' + d as u8
272                    } else {
273                        b'a' + (d - 10) as u8
274                    }
275                };
276                let buf = [
277                    b'\\',
278                    b'u',
279                    to_hex((code_point >> 12) & 0xF),
280                    to_hex((code_point >> 8) & 0xF),
281                    to_hex((code_point >> 4) & 0xF),
282                    to_hex(code_point & 0xF),
283                ];
284                self.out.extend_from_slice(&buf);
285            }
286            c if c.is_ascii() => {
287                self.out.push(c as u8);
288            }
289            c => {
290                let mut buf = [0u8; 4];
291                let len = c.encode_utf8(&mut buf).len();
292                self.out.extend_from_slice(&buf[..len]);
293            }
294        }
295    }
296
297    #[inline]
298    fn write_hex_byte(&mut self, byte: u8) {
299        let hi = byte >> 4;
300        let lo = byte & 0x0f;
301        self.out
302            .push(if hi < 10 { b'0' + hi } else { b'a' + (hi - 10) });
303        self.out
304            .push(if lo < 10 { b'0' + lo } else { b'a' + (lo - 10) });
305    }
306
307    #[inline]
308    fn write_u8_decimal(&mut self, value: u8) {
309        #[cfg(feature = "fast")]
310        self.out
311            .extend_from_slice(itoa::Buffer::new().format(value).as_bytes());
312        #[cfg(not(feature = "fast"))]
313        self.out.extend_from_slice(value.to_string().as_bytes());
314    }
315
316    fn write_bytes_array(&mut self, bytes: &[u8]) {
317        self.out.push(b'[');
318        for (index, byte) in bytes.iter().copied().enumerate() {
319            if index != 0 {
320                self.out.push(b',');
321            }
322            self.write_u8_decimal(byte);
323        }
324        self.out.push(b']');
325    }
326
327    fn write_bytes_hex(&mut self, bytes: &[u8], options: HexBytesOptions) {
328        self.out.push(b'"');
329        self.out.extend_from_slice(b"0x");
330
331        let should_truncate = options
332            .truncate_above
333            .is_some_and(|max_len| bytes.len() > max_len);
334        if !should_truncate {
335            for byte in bytes {
336                self.write_hex_byte(*byte);
337            }
338            self.out.push(b'"');
339            return;
340        }
341
342        let head = options.head.min(bytes.len());
343        let max_tail = bytes.len().saturating_sub(head);
344        let tail = options.tail.min(max_tail);
345
346        for byte in &bytes[..head] {
347            self.write_hex_byte(*byte);
348        }
349
350        let omitted = bytes.len().saturating_sub(head + tail);
351        self.out.extend_from_slice(b"..<");
352        #[cfg(feature = "fast")]
353        self.out
354            .extend_from_slice(itoa::Buffer::new().format(omitted).as_bytes());
355        #[cfg(not(feature = "fast"))]
356        self.out.extend_from_slice(omitted.to_string().as_bytes());
357        self.out.extend_from_slice(b" bytes>..");
358
359        if tail != 0 {
360            for byte in &bytes[bytes.len() - tail..] {
361                self.write_hex_byte(*byte);
362            }
363        }
364
365        self.out.push(b'"');
366    }
367
368    fn write_bytes_with_options(&mut self, bytes: &[u8]) {
369        match self.options.bytes_format {
370            BytesFormat::Array => self.write_bytes_array(bytes),
371            BytesFormat::Hex(options) => self.write_bytes_hex(bytes, options),
372        }
373    }
374}
375
376/// Check if any byte in the u128 equals the target byte.
377/// Uses the SWAR (SIMD Within A Register) technique.
378#[inline]
379const fn contains_byte(val: u128, byte: u8) -> bool {
380    let mask = 0x01010101010101010101010101010101u128 * (byte as u128);
381    let xor_result = val ^ mask;
382    let has_zero = (xor_result.wrapping_sub(0x01010101010101010101010101010101))
383        & !xor_result
384        & 0x80808080808080808080808080808080;
385    has_zero != 0
386}
387
388/// Check that all bytes have at least one of the top 3 bits set (i.e., >= 0x20).
389/// This means no control characters (0x00-0x1F).
390#[inline]
391const fn no_control_chars(value: u128) -> bool {
392    let masked = value & 0xe0e0e0e0e0e0e0e0e0e0e0e0e0e0e0e0;
393    let has_zero = (masked.wrapping_sub(0x01010101010101010101010101010101))
394        & !masked
395        & 0x80808080808080808080808080808080;
396    has_zero == 0
397}
398
399impl Default for JsonSerializer {
400    fn default() -> Self {
401        Self::new()
402    }
403}
404
405impl FormatSerializer for JsonSerializer {
406    type Error = JsonSerializeError;
407
408    fn begin_struct(&mut self) -> Result<(), Self::Error> {
409        self.before_value()?;
410        self.out.push(b'{');
411        self.stack.push(Ctx::Struct { first: true });
412        Ok(())
413    }
414
415    fn field_key(&mut self, key: &str) -> Result<(), Self::Error> {
416        match self.stack.last_mut() {
417            Some(Ctx::Struct { first }) => {
418                if !*first {
419                    self.out.push(b',');
420                }
421                *first = false;
422                self.write_indent();
423                self.write_json_string(key);
424                self.out.push(b':');
425                if self.options.pretty {
426                    self.out.push(b' ');
427                }
428                Ok(())
429            }
430            _ => Err(JsonSerializeError {
431                msg: "field_key called outside of a struct",
432            }),
433        }
434    }
435
436    fn end_struct(&mut self) -> Result<(), Self::Error> {
437        match self.stack.pop() {
438            Some(Ctx::Struct { first }) => {
439                // Only add newline/indent before closing brace if struct was non-empty
440                if !first {
441                    self.write_indent();
442                }
443                self.out.push(b'}');
444                Ok(())
445            }
446            _ => Err(JsonSerializeError {
447                msg: "end_struct called without matching begin_struct",
448            }),
449        }
450    }
451
452    fn begin_seq(&mut self) -> Result<(), Self::Error> {
453        self.before_value()?;
454        self.out.push(b'[');
455        self.stack.push(Ctx::Seq { first: true });
456        Ok(())
457    }
458
459    fn end_seq(&mut self) -> Result<(), Self::Error> {
460        match self.stack.pop() {
461            Some(Ctx::Seq { first }) => {
462                // Only add newline/indent before closing bracket if seq was non-empty
463                if !first {
464                    self.write_indent();
465                }
466                self.out.push(b']');
467                Ok(())
468            }
469            _ => Err(JsonSerializeError {
470                msg: "end_seq called without matching begin_seq",
471            }),
472        }
473    }
474
475    fn scalar(&mut self, scalar: ScalarValue<'_>) -> Result<(), Self::Error> {
476        self.before_value()?;
477        match scalar {
478            ScalarValue::Null | ScalarValue::Unit => self.out.extend_from_slice(b"null"),
479            ScalarValue::Bool(v) => {
480                if v {
481                    self.out.extend_from_slice(b"true")
482                } else {
483                    self.out.extend_from_slice(b"false")
484                }
485            }
486            ScalarValue::Char(c) => {
487                self.out.push(b'"');
488                self.write_json_escaped_char(c);
489                self.out.push(b'"');
490            }
491            ScalarValue::I64(v) => {
492                #[cfg(feature = "fast")]
493                self.out
494                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
495                #[cfg(not(feature = "fast"))]
496                self.out.extend_from_slice(v.to_string().as_bytes());
497            }
498            ScalarValue::U64(v) => {
499                #[cfg(feature = "fast")]
500                self.out
501                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
502                #[cfg(not(feature = "fast"))]
503                self.out.extend_from_slice(v.to_string().as_bytes());
504            }
505            ScalarValue::I128(v) => {
506                #[cfg(feature = "fast")]
507                self.out
508                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
509                #[cfg(not(feature = "fast"))]
510                self.out.extend_from_slice(v.to_string().as_bytes());
511            }
512            ScalarValue::U128(v) => {
513                #[cfg(feature = "fast")]
514                self.out
515                    .extend_from_slice(itoa::Buffer::new().format(v).as_bytes());
516                #[cfg(not(feature = "fast"))]
517                self.out.extend_from_slice(v.to_string().as_bytes());
518            }
519            ScalarValue::F64(v) => {
520                if v.is_nan() || v.is_infinite() {
521                    self.out.extend_from_slice(b"null");
522                } else {
523                    #[cfg(feature = "fast")]
524                    self.out
525                        .extend_from_slice(zmij::Buffer::new().format(v).as_bytes());
526                    #[cfg(not(feature = "fast"))]
527                    self.out.extend_from_slice(v.to_string().as_bytes());
528                }
529            }
530            ScalarValue::Str(s) => self.write_json_string(&s),
531            ScalarValue::Bytes(bytes) => self.write_bytes_with_options(bytes.as_ref()),
532        }
533        Ok(())
534    }
535
536    fn serialize_byte_sequence(&mut self, bytes: &[u8]) -> Result<bool, Self::Error> {
537        self.before_value()?;
538        self.write_bytes_with_options(bytes);
539        Ok(true)
540    }
541
542    fn serialize_byte_array(&mut self, bytes: &[u8]) -> Result<bool, Self::Error> {
543        self.serialize_byte_sequence(bytes)
544    }
545
546    fn raw_serialize_shape(&self) -> Option<&'static facet_core::Shape> {
547        Some(crate::RawJson::SHAPE)
548    }
549
550    fn raw_scalar(&mut self, content: &str) -> Result<(), Self::Error> {
551        // For RawJson, output the content directly without escaping
552        self.before_value()?;
553        self.out.extend_from_slice(content.as_bytes());
554        Ok(())
555    }
556
557    fn format_namespace(&self) -> Option<&'static str> {
558        Some("json")
559    }
560}
561
562/// Serialize a value to JSON bytes.
563///
564/// # Example
565///
566/// ```
567/// use facet::Facet;
568/// use facet_json::to_vec;
569///
570/// #[derive(Facet)]
571/// struct Point { x: i32, y: i32 }
572///
573/// let point = Point { x: 10, y: 20 };
574/// let bytes = to_vec(&point).unwrap();
575/// assert_eq!(bytes, br#"{"x":10,"y":20}"#);
576/// ```
577pub fn to_vec<'facet, T>(value: &'_ T) -> Result<Vec<u8>, SerializeError<JsonSerializeError>>
578where
579    T: Facet<'facet> + ?Sized,
580{
581    to_vec_with_options(value, &SerializeOptions::default())
582}
583
584/// Serialize a value to pretty-printed JSON bytes.
585///
586/// # Example
587///
588/// ```
589/// use facet::Facet;
590/// use facet_json::to_vec_pretty;
591///
592/// #[derive(Facet)]
593/// struct Point { x: i32, y: i32 }
594///
595/// let point = Point { x: 10, y: 20 };
596/// let bytes = to_vec_pretty(&point).unwrap();
597/// assert!(bytes.contains(&b'\n'));
598/// ```
599pub fn to_vec_pretty<'facet, T>(value: &'_ T) -> Result<Vec<u8>, SerializeError<JsonSerializeError>>
600where
601    T: Facet<'facet> + ?Sized,
602{
603    to_vec_with_options(value, &SerializeOptions::default().pretty())
604}
605
606/// Serialize a value to JSON bytes with custom options.
607///
608/// # Example
609///
610/// ```
611/// use facet::Facet;
612/// use facet_json::{to_vec_with_options, SerializeOptions};
613///
614/// #[derive(Facet)]
615/// struct Point { x: i32, y: i32 }
616///
617/// let point = Point { x: 10, y: 20 };
618///
619/// // Compact output
620/// let bytes = to_vec_with_options(&point, &SerializeOptions::default()).unwrap();
621/// assert_eq!(bytes, br#"{"x":10,"y":20}"#);
622///
623/// // Pretty output with tabs
624/// let bytes = to_vec_with_options(&point, &SerializeOptions::default().indent("\t")).unwrap();
625/// assert!(bytes.contains(&b'\n'));
626/// ```
627pub fn to_vec_with_options<'facet, T>(
628    value: &'_ T,
629    options: &SerializeOptions,
630) -> Result<Vec<u8>, SerializeError<JsonSerializeError>>
631where
632    T: Facet<'facet> + ?Sized,
633{
634    let mut serializer = JsonSerializer::with_options(options.clone());
635    serialize_root(&mut serializer, Peek::new(value))?;
636    Ok(serializer.finish())
637}
638
639/// Serialize a value to a JSON string.
640///
641/// # Example
642///
643/// ```
644/// use facet::Facet;
645/// use facet_json::to_string;
646///
647/// #[derive(Facet)]
648/// struct Person { name: String, age: u32 }
649///
650/// let person = Person { name: "Alice".into(), age: 30 };
651/// let json = to_string(&person).unwrap();
652/// assert_eq!(json, r#"{"name":"Alice","age":30}"#);
653/// ```
654pub fn to_string<'facet, T>(value: &'_ T) -> Result<String, SerializeError<JsonSerializeError>>
655where
656    T: Facet<'facet> + ?Sized,
657{
658    let bytes = to_vec(value)?;
659    // JSON output is always valid UTF-8, so this unwrap is safe
660    Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
661}
662
663/// Serialize a value to a pretty-printed JSON string.
664///
665/// # Example
666///
667/// ```
668/// use facet::Facet;
669/// use facet_json::to_string_pretty;
670///
671/// #[derive(Facet)]
672/// struct Person { name: String, age: u32 }
673///
674/// let person = Person { name: "Alice".into(), age: 30 };
675/// let json = to_string_pretty(&person).unwrap();
676/// assert!(json.contains('\n'));
677/// ```
678pub fn to_string_pretty<'facet, T>(
679    value: &'_ T,
680) -> Result<String, SerializeError<JsonSerializeError>>
681where
682    T: Facet<'facet> + ?Sized,
683{
684    let bytes = to_vec_pretty(value)?;
685    Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
686}
687
688/// Serialize a value to a JSON string with custom options.
689///
690/// # Example
691///
692/// ```
693/// use facet::Facet;
694/// use facet_json::{to_string_with_options, SerializeOptions};
695///
696/// #[derive(Facet)]
697/// struct Person { name: String, age: u32 }
698///
699/// let person = Person { name: "Alice".into(), age: 30 };
700///
701/// // Compact output
702/// let json = to_string_with_options(&person, &SerializeOptions::default()).unwrap();
703/// assert_eq!(json, r#"{"name":"Alice","age":30}"#);
704///
705/// // Pretty output with tabs
706/// let json = to_string_with_options(&person, &SerializeOptions::default().indent("\t")).unwrap();
707/// assert!(json.contains('\n'));
708/// ```
709pub fn to_string_with_options<'facet, T>(
710    value: &'_ T,
711    options: &SerializeOptions,
712) -> Result<String, SerializeError<JsonSerializeError>>
713where
714    T: Facet<'facet> + ?Sized,
715{
716    let bytes = to_vec_with_options(value, options)?;
717    Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
718}
719
720// ── Peek-based serialization ──
721
722/// Serialize a `Peek` instance to a JSON string.
723///
724/// This allows serializing values without requiring ownership, useful when
725/// you already have a `Peek` from reflection operations.
726///
727/// # Example
728///
729/// ```
730/// use facet::Facet;
731/// use facet_reflect::Peek;
732/// use facet_json::peek_to_string;
733///
734/// #[derive(Facet)]
735/// struct Point { x: i32, y: i32 }
736///
737/// let point = Point { x: 10, y: 20 };
738/// let json = peek_to_string(Peek::new(&point)).unwrap();
739/// assert_eq!(json, r#"{"x":10,"y":20}"#);
740/// ```
741pub fn peek_to_string<'input, 'facet>(
742    peek: Peek<'input, 'facet>,
743) -> Result<String, SerializeError<JsonSerializeError>> {
744    peek_to_string_with_options(peek, &SerializeOptions::default())
745}
746
747/// Serialize a `Peek` instance to a pretty-printed JSON string.
748///
749/// # Example
750///
751/// ```
752/// use facet::Facet;
753/// use facet_reflect::Peek;
754/// use facet_json::peek_to_string_pretty;
755///
756/// #[derive(Facet)]
757/// struct Point { x: i32, y: i32 }
758///
759/// let point = Point { x: 10, y: 20 };
760/// let json = peek_to_string_pretty(Peek::new(&point)).unwrap();
761/// assert!(json.contains('\n'));
762/// ```
763pub fn peek_to_string_pretty<'input, 'facet>(
764    peek: Peek<'input, 'facet>,
765) -> Result<String, SerializeError<JsonSerializeError>> {
766    peek_to_string_with_options(peek, &SerializeOptions::default().pretty())
767}
768
769/// Serialize a `Peek` instance to a JSON string with custom options.
770///
771/// # Example
772///
773/// ```
774/// use facet::Facet;
775/// use facet_reflect::Peek;
776/// use facet_json::{peek_to_string_with_options, SerializeOptions};
777///
778/// #[derive(Facet)]
779/// struct Point { x: i32, y: i32 }
780///
781/// let point = Point { x: 10, y: 20 };
782/// let json = peek_to_string_with_options(
783///     Peek::new(&point),
784///     &SerializeOptions::default().indent("\t"),
785/// ).unwrap();
786/// assert!(json.contains('\n'));
787/// ```
788pub fn peek_to_string_with_options<'input, 'facet>(
789    peek: Peek<'input, 'facet>,
790    options: &SerializeOptions,
791) -> Result<String, SerializeError<JsonSerializeError>> {
792    let mut serializer = JsonSerializer::with_options(options.clone());
793    serialize_root(&mut serializer, peek)?;
794    let bytes = serializer.finish();
795    Ok(String::from_utf8(bytes).expect("JSON output should always be valid UTF-8"))
796}
797
798// ── Writer-based serialization (std::io::Write) ──
799
800/// Serialize a value to JSON and write it to a `std::io::Write` writer.
801///
802/// # Example
803///
804/// ```
805/// use facet::Facet;
806/// use facet_json::to_writer_std;
807///
808/// #[derive(Facet)]
809/// struct Person {
810///     name: String,
811///     age: u32,
812/// }
813///
814/// let person = Person { name: "Alice".into(), age: 30 };
815/// let mut buffer = Vec::new();
816/// to_writer_std(&mut buffer, &person).unwrap();
817/// assert_eq!(buffer, br#"{"name":"Alice","age":30}"#);
818/// ```
819pub fn to_writer_std<'facet, W, T>(writer: W, value: &T) -> std::io::Result<()>
820where
821    W: std::io::Write,
822    T: Facet<'facet> + ?Sized,
823{
824    peek_to_writer_std(writer, Peek::new(value))
825}
826
827/// Serialize a value to pretty-printed JSON and write it to a `std::io::Write` writer.
828///
829/// # Example
830///
831/// ```
832/// use facet::Facet;
833/// use facet_json::to_writer_std_pretty;
834///
835/// #[derive(Facet)]
836/// struct Person {
837///     name: String,
838///     age: u32,
839/// }
840///
841/// let person = Person { name: "Alice".into(), age: 30 };
842/// let mut buffer = Vec::new();
843/// to_writer_std_pretty(&mut buffer, &person).unwrap();
844/// assert!(String::from_utf8_lossy(&buffer).contains('\n'));
845/// ```
846pub fn to_writer_std_pretty<'facet, W, T>(writer: W, value: &T) -> std::io::Result<()>
847where
848    W: std::io::Write,
849    T: Facet<'facet> + ?Sized,
850{
851    peek_to_writer_std_pretty(writer, Peek::new(value))
852}
853
854/// Serialize a value to JSON with custom options and write it to a `std::io::Write` writer.
855///
856/// # Example
857///
858/// ```
859/// use facet::Facet;
860/// use facet_json::{to_writer_std_with_options, SerializeOptions};
861///
862/// #[derive(Facet)]
863/// struct Person {
864///     name: String,
865///     age: u32,
866/// }
867///
868/// let person = Person { name: "Alice".into(), age: 30 };
869///
870/// // Compact output
871/// let mut buffer = Vec::new();
872/// to_writer_std_with_options(&mut buffer, &person, &SerializeOptions::default()).unwrap();
873/// assert_eq!(buffer, br#"{"name":"Alice","age":30}"#);
874///
875/// // Pretty output with tabs
876/// let mut buffer = Vec::new();
877/// to_writer_std_with_options(&mut buffer, &person, &SerializeOptions::default().indent("\t")).unwrap();
878/// assert!(String::from_utf8_lossy(&buffer).contains('\n'));
879/// ```
880pub fn to_writer_std_with_options<'facet, W, T>(
881    writer: W,
882    value: &T,
883    options: &SerializeOptions,
884) -> std::io::Result<()>
885where
886    W: std::io::Write,
887    T: Facet<'facet> + ?Sized,
888{
889    peek_to_writer_std_with_options(writer, Peek::new(value), options)
890}
891
892/// Serialize a `Peek` instance to JSON and write it to a `std::io::Write` writer.
893pub fn peek_to_writer_std<'input, 'facet, W>(
894    writer: W,
895    peek: Peek<'input, 'facet>,
896) -> std::io::Result<()>
897where
898    W: std::io::Write,
899{
900    peek_to_writer_std_with_options(writer, peek, &SerializeOptions::default())
901}
902
903/// Serialize a `Peek` instance to pretty-printed JSON and write it to a `std::io::Write` writer.
904pub fn peek_to_writer_std_pretty<'input, 'facet, W>(
905    writer: W,
906    peek: Peek<'input, 'facet>,
907) -> std::io::Result<()>
908where
909    W: std::io::Write,
910{
911    peek_to_writer_std_with_options(writer, peek, &SerializeOptions::default().pretty())
912}
913
914/// Serialize a `Peek` instance to JSON with custom options and write it to a `std::io::Write` writer.
915pub fn peek_to_writer_std_with_options<'input, 'facet, W>(
916    mut writer: W,
917    peek: Peek<'input, 'facet>,
918    options: &SerializeOptions,
919) -> std::io::Result<()>
920where
921    W: std::io::Write,
922{
923    // Serialize to bytes first, then write
924    // This is simpler and avoids the complexity of incremental writes
925    let mut serializer = JsonSerializer::with_options(options.clone());
926    serialize_root(&mut serializer, peek)
927        .map_err(|e| std::io::Error::other(alloc::format!("{:?}", e)))?;
928    writer.write_all(&serializer.finish())
929}
930
931#[cfg(test)]
932mod tests {
933    use facet::Facet;
934
935    use super::{BytesFormat, HexBytesOptions, SerializeOptions, to_string_with_options};
936
937    #[derive(Facet)]
938    struct BytesVec {
939        data: Vec<u8>,
940    }
941
942    #[derive(Facet)]
943    struct BytesArray {
944        data: [u8; 4],
945    }
946
947    #[test]
948    fn bytes_default_to_json_array() {
949        let value = BytesVec {
950            data: vec![0, 127, 255],
951        };
952
953        let json = to_string_with_options(&value, &SerializeOptions::default()).unwrap();
954        assert_eq!(json, r#"{"data":[0,127,255]}"#);
955    }
956
957    #[test]
958    fn bytes_can_serialize_as_hex_string() {
959        let value = BytesVec {
960            data: vec![0x00, 0x7f, 0xff],
961        };
962
963        let json =
964            to_string_with_options(&value, &SerializeOptions::default().bytes_as_hex()).unwrap();
965        assert_eq!(json, r#"{"data":"0x007fff"}"#);
966    }
967
968    #[test]
969    fn bytes_can_serialize_as_truncated_hex_string() {
970        let value = BytesVec {
971            data: (0u8..10).collect(),
972        };
973
974        let options = SerializeOptions::default()
975            .bytes_as_hex_with_options(HexBytesOptions::new().truncate(6).head_tail(2, 2));
976        let json = to_string_with_options(&value, &options).unwrap();
977        assert_eq!(json, r#"{"data":"0x0001..<6 bytes>..0809"}"#);
978    }
979
980    #[test]
981    fn byte_arrays_use_same_hex_mode() {
982        let value = BytesArray {
983            data: [0xaa, 0xbb, 0xcc, 0xdd],
984        };
985
986        let options =
987            SerializeOptions::default().bytes_format(BytesFormat::Hex(HexBytesOptions::new()));
988        let json = to_string_with_options(&value, &options).unwrap();
989        assert_eq!(json, r#"{"data":"0xaabbccdd"}"#);
990    }
991}