Skip to main content

serde_json_fmt/
lib.rs

1//! The `serde-json-fmt` crate lets you create custom [`serde_json`] formatters
2//! with the indentation, separators, and ASCII requirements of your choice.
3//!
4//! `serde_json` itself only directly provides the ability to produce JSON in
5//! either "compact" form or "pretty" form, with the only customizable aspect
6//! being the string used for pretty indentation.  `serde-json-fmt` complements
7//! `serde_json` to let you also customize the whitespace around commas &
8//! colons and whether to escape non-ASCII characters.
9//!
10//! # Examples
11//!
12//! Say you want to serialize a value in one-line "compact" form, but you want
13//! a space after each colon & comma, something that `serde_json`'s compact
14//! form doesn't do.  `serde-json-fmt` lets you do that:
15//!
16//! ```
17//! use serde_json::json;
18//! use serde_json_fmt::JsonFormat;
19//!
20//! let value = json!({
21//!     "colors": ["red", "blue", "taupe"],
22//!     "sub": {
23//!         "name": "Foo",
24//!         "on": true,
25//!         "size": 17
26//!     }
27//! });
28//!
29//! let s = JsonFormat::new()
30//!     .comma(", ")
31//!     .unwrap()
32//!     .colon(": ")
33//!     .unwrap()
34//!     .format_to_string(&value)
35//!     .unwrap();
36//!
37//! assert_eq!(
38//!     s,
39//!     r#"{"colors": ["red", "blue", "taupe"], "sub": {"name": "Foo", "on": true, "size": 17}}"#
40//! );
41//! ```
42//!
43//! Say you want to format a value in multiline "pretty" form, but using
44//! four-space indents and with all non-ASCII characters encoded as `\uXXXX`
45//! escape sequences.  `serde-json-fmt` lets you do that:
46//!
47//! ```
48//! use serde_json::json;
49//! use serde_json_fmt::JsonFormat;
50//!
51//! let value = json!({
52//!     "emojis": {
53//!         "goat":"🐐",
54//!         "pineapple": "🍍",
55//!         "smile": "😀",
56//!     },
57//!     "greek": {
58//!         "α": "alpha",
59//!         "β": "beta",
60//!         "γ": "gamma",
61//!     }
62//! });
63//!
64//! let s = JsonFormat::pretty()
65//!     .indent_width(Some(4))
66//!     .ascii(true)
67//!     .format_to_string(&value)
68//!     .unwrap();
69//!
70//! assert_eq!(s, r#"{
71//!     "emojis": {
72//!         "goat": "\ud83d\udc10",
73//!         "pineapple": "\ud83c\udf4d",
74//!         "smile": "\ud83d\ude00"
75//!     },
76//!     "greek": {
77//!         "\u03b1": "alpha",
78//!         "\u03b2": "beta",
79//!         "\u03b3": "gamma"
80//!     }
81//! }"#);
82//! ```
83
84use compact_str::CompactString;
85use serde::Serialize;
86use serde_json::ser::Formatter;
87use serde_json::Serializer;
88use std::fmt;
89use std::io::{self, Write};
90
91/// A [`Formatter`] builder for configuring JSON serialization options.
92///
93/// This type is the "entry point" to `serde-json-fmt`'s functionality.  To
94/// perform custom-formatted JSON serialization, start by creating a
95/// `JsonFormat` instance by calling either [`JsonFormat::new()`] or
96/// [`JsonFormat::pretty()`], then call the various configuration methods as
97/// desired, then either pass your [`serde::Serialize`] value to one of the
98/// [`format_to_string()`][JsonFormat::format_to_string],
99/// [`format_to_vec()`][JsonFormat::format_to_vec], and
100/// [`format_to_writer()`][JsonFormat::format_to_writer] convenience methods or
101/// else (for lower-level usage) call [`build()`][JsonFormat::build] or
102/// [`as_formatter()`][JsonFormat::as_formatter] to acquire a
103/// [`serde_json::ser::Formatter`] instance.
104#[derive(Clone, Debug, Eq, Hash, PartialEq)]
105pub struct JsonFormat {
106    indent: Option<CompactString>,
107    comma: CompactString,
108    colon: CompactString,
109    ascii: bool,
110}
111
112impl JsonFormat {
113    /// Create a new `JsonFormat` instance that starts out configured to use
114    /// `serde_json`'s "compact" format.  Specifically, the instance is
115    /// configured as follows:
116    ///
117    /// - `indent(None)`
118    /// - `comma(",")`
119    /// - `colon(":")`
120    /// - `ascii(false)`
121    pub fn new() -> Self {
122        JsonFormat {
123            indent: None,
124            comma: ",".into(),
125            colon: ":".into(),
126            ascii: false,
127        }
128    }
129
130    /// Create a new `JsonFormat` instance that starts out configured to use
131    /// `serde_json`'s "pretty" format.  Specifically, the instance is
132    /// configured as follows:
133    ///
134    /// - `indent(Some("  "))` (two spaces)
135    /// - `comma(",")`
136    /// - `colon(": ")`
137    /// - `ascii(false)`
138    pub fn pretty() -> Self {
139        JsonFormat {
140            indent: Some("  ".into()),
141            comma: ",".into(),
142            colon: ": ".into(),
143            ascii: false,
144        }
145    }
146
147    /// Set whether non-ASCII characters in strings should be serialized as
148    /// ASCII using `\uXXXX` escape sequences.  If `flag` is `true`, then all
149    /// non-ASCII characters will be escaped; if `flag` is `false`, then
150    /// non-ASCII characters will be serialized as themselves.
151    pub fn ascii(mut self, flag: bool) -> Self {
152        self.ascii = flag;
153        self
154    }
155
156    /// Set the string to use as the item separator in lists & objects.
157    ///
158    /// `s` must contain exactly one comma (`,`) character; all other
159    /// characters must be space characters, tabs, line feeds, and/or carriage
160    /// returns.
161    ///
162    /// # Errors
163    ///
164    /// Returns `Err` if `s` does not meet the above requirements.
165    pub fn comma<S: AsRef<str>>(mut self, s: S) -> Result<Self, JsonSyntaxError> {
166        self.comma = validate_string(s, Some(','))?;
167        Ok(self)
168    }
169
170    /// Set the string to use as the key-value separator in objects.
171    ///
172    /// `s` must contain exactly one colon (`:`) character; all other
173    /// characters must be space characters, tabs, line feeds, and/or carriage
174    /// returns.
175    ///
176    /// # Errors
177    ///
178    /// Returns `Err` if `s` does not meet the above requirements.
179    pub fn colon<S: AsRef<str>>(mut self, s: S) -> Result<Self, JsonSyntaxError> {
180        self.colon = validate_string(s, Some(':'))?;
181        Ok(self)
182    }
183
184    /// Set the string used for indentation.
185    ///
186    /// If `s` is `None`, then no indentation or newlines will be inserted when
187    /// serializing.  If `s` is `Some("")` (an empty string), then newlines
188    /// will be inserted, but nothing will be indented.  If `s` contains any
189    /// other string, the string must consist entirely of space characters,
190    /// tabs, line feeds, and/or carriage returns.
191    ///
192    /// # Errors
193    ///
194    /// Returns `Err` if `s` contains a string that contains any character
195    /// other than those listed above.
196    pub fn indent<S: AsRef<str>>(mut self, s: Option<S>) -> Result<Self, JsonSyntaxError> {
197        self.indent = s.map(|s| validate_string(s, None)).transpose()?;
198        Ok(self)
199    }
200
201    /// Set the string used for indentation to the given number of spaces.
202    ///
203    /// This method is a convenience wrapper around
204    /// [`indent()`][JsonFormat::indent] that calls it with a string consisting
205    /// of the given number of space characters, or with `None` if `n` is
206    /// `None`.
207    pub fn indent_width(self, n: Option<usize>) -> Self {
208        let Ok(me) = self.indent(n.map(|i| CompactString::from(" ").repeat(i))) else {
209            unreachable!("repeated spaces should be valid indentation");
210        };
211        me
212    }
213
214    /// Format a [`serde::Serialize`] value to a [`String`] as JSON using the
215    /// configured formatting options.
216    ///
217    /// # Errors
218    ///
219    /// Has the same error conditions as [`serde_json::to_string()`].
220    pub fn format_to_string<T: ?Sized + Serialize>(
221        &self,
222        value: &T,
223    ) -> Result<String, serde_json::Error> {
224        let bytes = self.format_to_vec(value)?;
225        let Ok(s) = String::from_utf8(bytes) else {
226            unreachable!("serialized JSON should be valid UTF-8");
227        };
228        Ok(s)
229    }
230
231    /// Format a [`serde::Serialize`] value to a [`Vec<u8>`] as JSON using the
232    /// configured formatting options.
233    ///
234    /// # Errors
235    ///
236    /// Has the same error conditions as [`serde_json::to_vec()`].
237    pub fn format_to_vec<T: ?Sized + Serialize>(
238        &self,
239        value: &T,
240    ) -> Result<Vec<u8>, serde_json::Error> {
241        let mut vec = Vec::with_capacity(128);
242        self.format_to_writer(&mut vec, value)?;
243        Ok(vec)
244    }
245
246    /// Write a [`serde::Serialize`] value to a [`std::io::Write`] instance as
247    /// JSON using the configured formatting options.
248    ///
249    /// # Errors
250    ///
251    /// Has the same error conditions as [`serde_json::to_writer()`].
252    pub fn format_to_writer<T: ?Sized + Serialize, W: Write>(
253        &self,
254        writer: W,
255        value: &T,
256    ) -> Result<(), serde_json::Error> {
257        let mut ser = Serializer::with_formatter(writer, self.as_formatter());
258        value.serialize(&mut ser)
259    }
260
261    /// Consume the `JsonFormat` instance and return a
262    /// [`serde_json::ser::Formatter`] instance.
263    ///
264    /// This is a low-level operation.  For most use cases, using one of the
265    /// [`format_to_string()`][JsonFormat::format_to_string],
266    /// [`format_to_vec()`][JsonFormat::format_to_vec], and
267    /// [`format_to_writer()`][JsonFormat::format_to_writer] convenience
268    /// methods is recommended.
269    pub fn build(self) -> JsonFormatter {
270        JsonFormatter::new(self)
271    }
272
273    /// Return a [`serde_json::ser::Formatter`] instance that borrows data from
274    /// the `JsonFormat` instance.
275    ///
276    /// This is a low-level operation.  For most use cases, using one of the
277    /// [`format_to_string()`][JsonFormat::format_to_string],
278    /// [`format_to_vec()`][JsonFormat::format_to_vec], and
279    /// [`format_to_writer()`][JsonFormat::format_to_writer] convenience
280    /// methods is recommended.
281    ///
282    /// Unlike [`build()`][JsonFormat::build], this method makes it possible to
283    /// create multiple `Formatter`s from a single `JsonFormat` instance.
284    pub fn as_formatter(&self) -> JsonFrmtr<'_> {
285        JsonFrmtr::new(internal::JsonFmt {
286            indent: self.indent.as_ref().map(CompactString::as_bytes),
287            comma: self.comma.as_bytes(),
288            colon: self.colon.as_bytes(),
289            ascii: self.ascii,
290        })
291    }
292}
293
294impl Default for JsonFormat {
295    /// Equivalent to [`JsonFormat::new()`]
296    fn default() -> Self {
297        JsonFormat::new()
298    }
299}
300
301// Workaround from <https://github.com/rust-lang/rust/issues/34537> for making
302// types in public interfaces private
303mod internal {
304    use super::*;
305
306    pub trait OptionsData {
307        fn indent(&self) -> Option<&[u8]>;
308        fn comma(&self) -> &[u8];
309        fn colon(&self) -> &[u8];
310        fn ascii(&self) -> bool;
311    }
312
313    impl OptionsData for JsonFormat {
314        fn indent(&self) -> Option<&[u8]> {
315            self.indent.as_ref().map(CompactString::as_bytes)
316        }
317
318        fn comma(&self) -> &[u8] {
319            self.comma.as_bytes()
320        }
321
322        fn colon(&self) -> &[u8] {
323            self.colon.as_bytes()
324        }
325
326        fn ascii(&self) -> bool {
327            self.ascii
328        }
329    }
330
331    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
332    pub struct JsonFmt<'a> {
333        pub indent: Option<&'a [u8]>,
334        pub comma: &'a [u8],
335        pub colon: &'a [u8],
336        pub ascii: bool,
337    }
338
339    impl OptionsData for JsonFmt<'_> {
340        fn indent(&self) -> Option<&[u8]> {
341            self.indent
342        }
343
344        fn comma(&self) -> &[u8] {
345            self.comma
346        }
347
348        fn colon(&self) -> &[u8] {
349            self.colon
350        }
351
352        fn ascii(&self) -> bool {
353            self.ascii
354        }
355    }
356
357    #[derive(Clone, Debug, Eq, Hash, PartialEq)]
358    pub struct JsonFormatterBase<O> {
359        indent_level: usize,
360        indent_next: bool,
361        options: O,
362    }
363
364    impl<O: OptionsData> JsonFormatterBase<O> {
365        pub fn new(options: O) -> Self {
366            JsonFormatterBase {
367                indent_level: 0,
368                indent_next: false,
369                options,
370            }
371        }
372
373        fn print_indent<W: ?Sized + Write>(&self, writer: &mut W) -> io::Result<()> {
374            if let Some(indent) = self.options.indent() {
375                writer.write_all(b"\n")?;
376                for _ in 0..self.indent_level {
377                    writer.write_all(indent)?;
378                }
379            }
380            Ok(())
381        }
382    }
383
384    impl<O: OptionsData> Formatter for JsonFormatterBase<O> {
385        fn begin_array<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
386            self.indent_level += 1;
387            self.indent_next = false;
388            writer.write_all(b"[")
389        }
390
391        fn begin_array_value<W: ?Sized + Write>(
392            &mut self,
393            writer: &mut W,
394            first: bool,
395        ) -> io::Result<()> {
396            if !first {
397                writer.write_all(self.options.comma())?;
398            }
399            self.print_indent(writer)
400        }
401
402        fn end_array_value<W: ?Sized + Write>(&mut self, _writer: &mut W) -> io::Result<()> {
403            self.indent_next = true;
404            Ok(())
405        }
406
407        fn end_array<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
408            self.indent_level -= 1;
409            if self.indent_next {
410                self.print_indent(writer)?;
411            }
412            writer.write_all(b"]")
413        }
414
415        fn begin_object<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
416            self.indent_level += 1;
417            self.indent_next = false;
418            writer.write_all(b"{")
419        }
420
421        fn begin_object_key<W: ?Sized + Write>(
422            &mut self,
423            writer: &mut W,
424            first: bool,
425        ) -> io::Result<()> {
426            if !first {
427                writer.write_all(self.options.comma())?;
428            }
429            self.print_indent(writer)
430        }
431
432        fn begin_object_value<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
433            writer.write_all(self.options.colon())
434        }
435
436        fn end_object_value<W: ?Sized + Write>(&mut self, _writer: &mut W) -> io::Result<()> {
437            self.indent_next = true;
438            Ok(())
439        }
440
441        fn end_object<W: ?Sized + Write>(&mut self, writer: &mut W) -> io::Result<()> {
442            self.indent_level -= 1;
443            if self.indent_next {
444                self.print_indent(writer)?;
445            }
446            writer.write_all(b"}")
447        }
448
449        fn write_string_fragment<W: ?Sized + Write>(
450            &mut self,
451            writer: &mut W,
452            fragment: &str,
453        ) -> io::Result<()> {
454            for ch in fragment.chars() {
455                if !self.options.ascii() || ch.is_ascii() {
456                    writer.write_all(ch.encode_utf8(&mut [0; 4]).as_bytes())?;
457                } else {
458                    for surrogate in ch.encode_utf16(&mut [0; 2]) {
459                        write!(writer, "\\u{surrogate:04x}")?;
460                    }
461                }
462            }
463            Ok(())
464        }
465    }
466}
467
468/// A [`serde_json::ser::Formatter`] type that owns its data.
469///
470/// Instances of this type are acquired by calling [`JsonFormat::build()`].
471pub type JsonFormatter = internal::JsonFormatterBase<JsonFormat>;
472
473/// A [`serde_json::ser::Formatter`] type that borrows its data from a
474/// [`JsonFormat`].
475///
476/// Instances of this type are acquired by calling
477/// [`JsonFormat::as_formatter()`].
478pub type JsonFrmtr<'a> = internal::JsonFormatterBase<internal::JsonFmt<'a>>;
479
480/// Error returned when an invalid string is passed to certain [`JsonFormat`]
481/// methods.
482#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
483pub enum JsonSyntaxError {
484    /// Returned when the given string contains an invalid/unexpected
485    /// character.  Contains the character in question.
486    InvalidCharacter(char),
487
488    /// Retured when a string passed to [`JsonFormat::comma()`] or
489    /// [`JsonFormat::colon()`] does not contain a comma or colon,
490    /// respectively.  Contains a comma or colon as appropriate.
491    MissingSeparator(char),
492
493    /// Retured when a string passed to [`JsonFormat::comma()`] or
494    /// [`JsonFormat::colon()`] contains more than one comma or colon,
495    /// respectively.  Contains a comma or colon as appropriate.
496    MultipleSeparators(char),
497}
498
499impl fmt::Display for JsonSyntaxError {
500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501        use JsonSyntaxError::*;
502        match self {
503            InvalidCharacter(c) => write!(f, "string contains unexpected character {c:?}"),
504            MissingSeparator(c) => write!(f, "no occurrence of {c:?} found in string"),
505            MultipleSeparators(c) => write!(f, "multiple occurrences of {c:?} found in string"),
506        }
507    }
508}
509
510impl std::error::Error for JsonSyntaxError {}
511
512fn validate_string<S: AsRef<str>>(
513    s: S,
514    sep: Option<char>,
515) -> Result<CompactString, JsonSyntaxError> {
516    let s = s.as_ref();
517    let mut seen_sep = false;
518    for ch in s.chars() {
519        match (sep, ch) {
520            (Some(sep_), ch) if sep_ == ch => {
521                if std::mem::replace(&mut seen_sep, true) {
522                    return Err(JsonSyntaxError::MultipleSeparators(sep_));
523                }
524            }
525            // RFC 8259, section 2
526            (_, ' ' | '\t' | '\n' | '\r') => (),
527            (_, ch) => return Err(JsonSyntaxError::InvalidCharacter(ch)),
528        }
529    }
530    if let Some(sep_) = sep {
531        if !seen_sep {
532            return Err(JsonSyntaxError::MissingSeparator(sep_));
533        }
534    }
535    Ok(s.into())
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541    use indoc::indoc;
542    use rstest::rstest;
543    use serde_json::json;
544
545    #[rstest]
546    #[case("?", Ok("?".into()))]
547    #[case(" ?", Ok(" ?".into()))]
548    #[case("? ", Ok("? ".into()))]
549    #[case("  ? ", Ok("  ? ".into()))]
550    #[case(" \t?\r\n", Ok(" \t?\r\n".into()))]
551    #[case("", Err(JsonSyntaxError::MissingSeparator('?')))]
552    #[case(" ", Err(JsonSyntaxError::MissingSeparator('?')))]
553    #[case("??", Err(JsonSyntaxError::MultipleSeparators('?')))]
554    #[case("? ?", Err(JsonSyntaxError::MultipleSeparators('?')))]
555    #[case("\x0C", Err(JsonSyntaxError::InvalidCharacter('\x0C')))]
556    #[case("\x0B", Err(JsonSyntaxError::InvalidCharacter('\x0B')))]
557    #[case("\u{A0}", Err(JsonSyntaxError::InvalidCharacter('\u{A0}')))]
558    #[case("\u{85}", Err(JsonSyntaxError::InvalidCharacter('\u{85}')))]
559    #[case("\u{1680}", Err(JsonSyntaxError::InvalidCharacter('\u{1680}')))]
560    #[case("\u{180E}", Err(JsonSyntaxError::InvalidCharacter('\u{180E}')))]
561    #[case("\u{2000}", Err(JsonSyntaxError::InvalidCharacter('\u{2000}')))]
562    #[case("\u{2001}", Err(JsonSyntaxError::InvalidCharacter('\u{2001}')))]
563    #[case("\u{2002}", Err(JsonSyntaxError::InvalidCharacter('\u{2002}')))]
564    #[case("\u{2003}", Err(JsonSyntaxError::InvalidCharacter('\u{2003}')))]
565    #[case("\u{2004}", Err(JsonSyntaxError::InvalidCharacter('\u{2004}')))]
566    #[case("\u{2005}", Err(JsonSyntaxError::InvalidCharacter('\u{2005}')))]
567    #[case("\u{2006}", Err(JsonSyntaxError::InvalidCharacter('\u{2006}')))]
568    #[case("\u{2007}", Err(JsonSyntaxError::InvalidCharacter('\u{2007}')))]
569    #[case("\u{2008}", Err(JsonSyntaxError::InvalidCharacter('\u{2008}')))]
570    #[case("\u{2009}", Err(JsonSyntaxError::InvalidCharacter('\u{2009}')))]
571    #[case("\u{200A}", Err(JsonSyntaxError::InvalidCharacter('\u{200A}')))]
572    #[case("\u{200B}", Err(JsonSyntaxError::InvalidCharacter('\u{200B}')))]
573    #[case("\u{200C}", Err(JsonSyntaxError::InvalidCharacter('\u{200C}')))]
574    #[case("\u{200D}", Err(JsonSyntaxError::InvalidCharacter('\u{200D}')))]
575    #[case("\u{2028}", Err(JsonSyntaxError::InvalidCharacter('\u{2028}')))]
576    #[case("\u{2029}", Err(JsonSyntaxError::InvalidCharacter('\u{2029}')))]
577    #[case("\u{202F}", Err(JsonSyntaxError::InvalidCharacter('\u{202F}')))]
578    #[case("\u{205F}", Err(JsonSyntaxError::InvalidCharacter('\u{205F}')))]
579    #[case("\u{2060}", Err(JsonSyntaxError::InvalidCharacter('\u{2060}')))]
580    #[case("\u{3000}", Err(JsonSyntaxError::InvalidCharacter('\u{3000}')))]
581    #[case("\u{FEFF}", Err(JsonSyntaxError::InvalidCharacter('\u{FEFF}')))]
582    #[case("\x0C?", Err(JsonSyntaxError::InvalidCharacter('\x0C')))]
583    #[case("?\x0C", Err(JsonSyntaxError::InvalidCharacter('\x0C')))]
584    #[case("?\x0C?", Err(JsonSyntaxError::InvalidCharacter('\x0C')))]
585    #[case("??\x0C", Err(JsonSyntaxError::MultipleSeparators('?')))]
586    #[case(".", Err(JsonSyntaxError::InvalidCharacter('.')))]
587    #[case(".?", Err(JsonSyntaxError::InvalidCharacter('.')))]
588    #[case("?.", Err(JsonSyntaxError::InvalidCharacter('.')))]
589    #[case("?.?", Err(JsonSyntaxError::InvalidCharacter('.')))]
590    #[case("??.", Err(JsonSyntaxError::MultipleSeparators('?')))]
591    #[case("☃", Err(JsonSyntaxError::InvalidCharacter('☃')))]
592    #[case("☃?", Err(JsonSyntaxError::InvalidCharacter('☃')))]
593    #[case("?☃", Err(JsonSyntaxError::InvalidCharacter('☃')))]
594    #[case("?☃?", Err(JsonSyntaxError::InvalidCharacter('☃')))]
595    #[case("??☃", Err(JsonSyntaxError::MultipleSeparators('?')))]
596    fn test_validate_string_sep(
597        #[case] s: &str,
598        #[case] r: Result<CompactString, JsonSyntaxError>,
599    ) {
600        assert_eq!(validate_string(s, Some('?')), r);
601    }
602
603    #[rstest]
604    #[case("", Ok("".into()))]
605    #[case(" ", Ok(" ".into()))]
606    #[case("    ", Ok("    ".into()))]
607    #[case(" \t\r\n", Ok(" \t\r\n".into()))]
608    #[case("?", Err(JsonSyntaxError::InvalidCharacter('?')))]
609    #[case(" ?", Err(JsonSyntaxError::InvalidCharacter('?')))]
610    #[case("? ", Err(JsonSyntaxError::InvalidCharacter('?')))]
611    #[case("  ? ", Err(JsonSyntaxError::InvalidCharacter('?')))]
612    #[case("??", Err(JsonSyntaxError::InvalidCharacter('?')))]
613    #[case("\x0C", Err(JsonSyntaxError::InvalidCharacter('\x0C')))]
614    #[case("\x0B", Err(JsonSyntaxError::InvalidCharacter('\x0B')))]
615    #[case("\u{A0}", Err(JsonSyntaxError::InvalidCharacter('\u{A0}')))]
616    #[case("\u{85}", Err(JsonSyntaxError::InvalidCharacter('\u{85}')))]
617    #[case("\u{1680}", Err(JsonSyntaxError::InvalidCharacter('\u{1680}')))]
618    #[case("\u{180E}", Err(JsonSyntaxError::InvalidCharacter('\u{180E}')))]
619    #[case("\u{2000}", Err(JsonSyntaxError::InvalidCharacter('\u{2000}')))]
620    #[case("\u{2001}", Err(JsonSyntaxError::InvalidCharacter('\u{2001}')))]
621    #[case("\u{2002}", Err(JsonSyntaxError::InvalidCharacter('\u{2002}')))]
622    #[case("\u{2003}", Err(JsonSyntaxError::InvalidCharacter('\u{2003}')))]
623    #[case("\u{2004}", Err(JsonSyntaxError::InvalidCharacter('\u{2004}')))]
624    #[case("\u{2005}", Err(JsonSyntaxError::InvalidCharacter('\u{2005}')))]
625    #[case("\u{2006}", Err(JsonSyntaxError::InvalidCharacter('\u{2006}')))]
626    #[case("\u{2007}", Err(JsonSyntaxError::InvalidCharacter('\u{2007}')))]
627    #[case("\u{2008}", Err(JsonSyntaxError::InvalidCharacter('\u{2008}')))]
628    #[case("\u{2009}", Err(JsonSyntaxError::InvalidCharacter('\u{2009}')))]
629    #[case("\u{200A}", Err(JsonSyntaxError::InvalidCharacter('\u{200A}')))]
630    #[case("\u{200B}", Err(JsonSyntaxError::InvalidCharacter('\u{200B}')))]
631    #[case("\u{200C}", Err(JsonSyntaxError::InvalidCharacter('\u{200C}')))]
632    #[case("\u{200D}", Err(JsonSyntaxError::InvalidCharacter('\u{200D}')))]
633    #[case("\u{2028}", Err(JsonSyntaxError::InvalidCharacter('\u{2028}')))]
634    #[case("\u{2029}", Err(JsonSyntaxError::InvalidCharacter('\u{2029}')))]
635    #[case("\u{202F}", Err(JsonSyntaxError::InvalidCharacter('\u{202F}')))]
636    #[case("\u{205F}", Err(JsonSyntaxError::InvalidCharacter('\u{205F}')))]
637    #[case("\u{2060}", Err(JsonSyntaxError::InvalidCharacter('\u{2060}')))]
638    #[case("\u{3000}", Err(JsonSyntaxError::InvalidCharacter('\u{3000}')))]
639    #[case("\u{FEFF}", Err(JsonSyntaxError::InvalidCharacter('\u{FEFF}')))]
640    #[case("\x0C ", Err(JsonSyntaxError::InvalidCharacter('\x0C')))]
641    #[case(" \x0C", Err(JsonSyntaxError::InvalidCharacter('\x0C')))]
642    #[case(" \x0C ", Err(JsonSyntaxError::InvalidCharacter('\x0C')))]
643    #[case(".", Err(JsonSyntaxError::InvalidCharacter('.')))]
644    #[case(". ", Err(JsonSyntaxError::InvalidCharacter('.')))]
645    #[case(" .", Err(JsonSyntaxError::InvalidCharacter('.')))]
646    #[case(" . ", Err(JsonSyntaxError::InvalidCharacter('.')))]
647    #[case("☃", Err(JsonSyntaxError::InvalidCharacter('☃')))]
648    #[case("☃ ", Err(JsonSyntaxError::InvalidCharacter('☃')))]
649    #[case(" ☃", Err(JsonSyntaxError::InvalidCharacter('☃')))]
650    #[case(" ☃ ", Err(JsonSyntaxError::InvalidCharacter('☃')))]
651    fn test_validate_string_no_sep(
652        #[case] s: &str,
653        #[case] r: Result<CompactString, JsonSyntaxError>,
654    ) {
655        assert_eq!(validate_string(s, None), r);
656    }
657
658    #[test]
659    fn test_format_default() {
660        let value = json!({
661            "colors": ["red", "blue", "taupe"],
662            "sub": {
663                "name": "Foo",
664                "on": true,
665                "size": 17
666            }
667        });
668        let s = JsonFormat::new().format_to_string(&value).unwrap();
669        assert_eq!(
670            s,
671            r#"{"colors":["red","blue","taupe"],"sub":{"name":"Foo","on":true,"size":17}}"#
672        );
673    }
674
675    #[test]
676    fn test_format_pretty() {
677        let value = json!({
678            "colors": ["red", "blue", "taupe"],
679            "sub": {
680                "name": "Foo",
681                "on": true,
682                "size": 17
683            }
684        });
685        let s = JsonFormat::pretty().format_to_string(&value).unwrap();
686        assert_eq!(
687            s,
688            indoc! {r#"{
689              "colors": [
690                "red",
691                "blue",
692                "taupe"
693              ],
694              "sub": {
695                "name": "Foo",
696                "on": true,
697                "size": 17
698              }
699            }"#}
700        );
701    }
702
703    #[test]
704    fn test_format_default_is_new() {
705        let value = json!({
706            "colors": ["red", "blue", "taupe"],
707            "sub": {
708                "name": "Foo",
709                "on": true,
710                "size": 17
711            }
712        });
713        assert_eq!(
714            JsonFormat::new().format_to_string(&value).unwrap(),
715            JsonFormat::default().format_to_string(&value).unwrap(),
716        );
717    }
718
719    #[test]
720    fn test_format_default_matches_serde_json() {
721        let value = json!({
722            "colors": ["red", "blue", "taupe"],
723            "sub": {
724                "name": "Foo",
725                "on": true,
726                "size": 17
727            }
728        });
729        assert_eq!(
730            JsonFormat::new().format_to_string(&value).unwrap(),
731            serde_json::to_string(&value).unwrap(),
732        );
733    }
734
735    #[test]
736    fn test_format_pretty_matches_serde_json() {
737        let value = json!({
738            "colors": ["red", "blue", "taupe"],
739            "sub": {
740                "name": "Foo",
741                "on": true,
742                "size": 17
743            }
744        });
745        assert_eq!(
746            JsonFormat::pretty().format_to_string(&value).unwrap(),
747            serde_json::to_string_pretty(&value).unwrap(),
748        );
749    }
750
751    #[test]
752    fn test_format_pretty_complicated() {
753        let value = json!({
754            "colors": [
755                "red",
756                "blue",
757                "taupe"
758            ],
759            "sampler": {
760                "empty_list": [],
761                "empty_object": {},
762                "nested": {
763                    "list": [
764                        1,
765                        {
766                            "strange": "charmed",
767                            "truth": "beauty",
768                            "up": "down"
769                        },
770                        3
771                    ],
772                },
773                "null": null,
774                "singleton_list": [
775                    42
776                ],
777                "singleton_object": {
778                    "key": "value"
779                }
780            },
781            "sub": {
782                "name": "Foo",
783                "size": 17,
784                "on": true
785            }
786        });
787        let s = JsonFormat::pretty().format_to_string(&value).unwrap();
788        assert_eq!(
789            s,
790            indoc! {r#"{
791              "colors": [
792                "red",
793                "blue",
794                "taupe"
795              ],
796              "sampler": {
797                "empty_list": [],
798                "empty_object": {},
799                "nested": {
800                  "list": [
801                    1,
802                    {
803                      "strange": "charmed",
804                      "truth": "beauty",
805                      "up": "down"
806                    },
807                    3
808                  ]
809                },
810                "null": null,
811                "singleton_list": [
812                  42
813                ],
814                "singleton_object": {
815                  "key": "value"
816                }
817              },
818              "sub": {
819                "name": "Foo",
820                "on": true,
821                "size": 17
822              }
823            }"#}
824        );
825    }
826
827    #[test]
828    fn test_format_pretty_complicated_indent_4() {
829        let value = json!({
830            "colors": [
831                "red",
832                "blue",
833                "taupe"
834            ],
835            "sampler": {
836                "empty_list": [],
837                "empty_object": {},
838                "nested": {
839                    "list": [
840                        1,
841                        {
842                            "strange": "charmed",
843                            "truth": "beauty",
844                            "up": "down"
845                        },
846                        3
847                    ],
848                },
849                "null": null,
850                "singleton_list": [
851                    42
852                ],
853                "singleton_object": {
854                    "key": "value"
855                }
856            },
857            "sub": {
858                "name": "Foo",
859                "size": 17,
860                "on": true
861            }
862        });
863        let s = JsonFormat::pretty()
864            .indent_width(Some(4))
865            .format_to_string(&value)
866            .unwrap();
867        assert_eq!(
868            s,
869            indoc! {r#"{
870                "colors": [
871                    "red",
872                    "blue",
873                    "taupe"
874                ],
875                "sampler": {
876                    "empty_list": [],
877                    "empty_object": {},
878                    "nested": {
879                        "list": [
880                            1,
881                            {
882                                "strange": "charmed",
883                                "truth": "beauty",
884                                "up": "down"
885                            },
886                            3
887                        ]
888                    },
889                    "null": null,
890                    "singleton_list": [
891                        42
892                    ],
893                    "singleton_object": {
894                        "key": "value"
895                    }
896                },
897                "sub": {
898                    "name": "Foo",
899                    "on": true,
900                    "size": 17
901                }
902            }"#}
903        );
904    }
905
906    #[test]
907    fn test_format_pretty_empty_indent() {
908        let value = json!({
909            "nested": {
910                "list": [
911                    1,
912                    {
913                        "strange": "charmed",
914                        "truth": "beauty",
915                        "up": "down"
916                    },
917                    3
918                ]
919            }
920        });
921        let s = JsonFormat::pretty()
922            .indent(Some(""))
923            .unwrap()
924            .format_to_string(&value)
925            .unwrap();
926        assert_eq!(
927            s,
928            indoc! {r#"{
929            "nested": {
930            "list": [
931            1,
932            {
933            "strange": "charmed",
934            "truth": "beauty",
935            "up": "down"
936            },
937            3
938            ]
939            }
940            }"#}
941        );
942    }
943
944    #[test]
945    fn test_format_pretty_zero_indent_width() {
946        let value = json!({
947            "nested": {
948                "list": [
949                    1,
950                    {
951                        "strange": "charmed",
952                        "truth": "beauty",
953                        "up": "down"
954                    },
955                    3
956                ]
957            }
958        });
959        let s = JsonFormat::pretty()
960            .indent_width(Some(0))
961            .format_to_string(&value)
962            .unwrap();
963        assert_eq!(
964            s,
965            indoc! {r#"{
966            "nested": {
967            "list": [
968            1,
969            {
970            "strange": "charmed",
971            "truth": "beauty",
972            "up": "down"
973            },
974            3
975            ]
976            }
977            }"#}
978        );
979    }
980
981    #[test]
982    fn test_format_pretty_tab_indent() {
983        let value = json!({
984            "nested": {
985                "list": [
986                    1,
987                    {
988                        "strange": "charmed",
989                        "truth": "beauty",
990                        "up": "down"
991                    },
992                    3
993                ]
994            }
995        });
996        let s = JsonFormat::pretty()
997            .indent(Some("\t"))
998            .unwrap()
999            .format_to_string(&value)
1000            .unwrap();
1001        assert_eq!(
1002            s,
1003            indoc! {"{
1004            \t\"nested\": {
1005            \t\t\"list\": [
1006            \t\t\t1,
1007            \t\t\t{
1008            \t\t\t\t\"strange\": \"charmed\",
1009            \t\t\t\t\"truth\": \"beauty\",
1010            \t\t\t\t\"up\": \"down\"
1011            \t\t\t},
1012            \t\t\t3
1013            \t\t]
1014            \t}
1015            }"}
1016        );
1017    }
1018
1019    #[test]
1020    fn test_format_spaced_separators() {
1021        let value = json!({
1022            "colors": ["red", "blue", "taupe"],
1023            "sub": {
1024                "name": "Foo",
1025                "on": true,
1026                "size": 17
1027            }
1028        });
1029        let s = JsonFormat::new()
1030            .comma(", ")
1031            .unwrap()
1032            .colon(": ")
1033            .unwrap()
1034            .format_to_string(&value)
1035            .unwrap();
1036        assert_eq!(
1037            s,
1038            r#"{"colors": ["red", "blue", "taupe"], "sub": {"name": "Foo", "on": true, "size": 17}}"#
1039        );
1040    }
1041
1042    #[test]
1043    fn test_format_weird_separators() {
1044        let value = json!({
1045            "colors": ["red", "blue", "taupe"],
1046            "sub": {
1047                "name": "Foo",
1048                "on": true,
1049                "size": 17
1050            }
1051        });
1052        let s = JsonFormat::new()
1053            .comma("\n,")
1054            .unwrap()
1055            .colon("\t:\t")
1056            .unwrap()
1057            .format_to_string(&value)
1058            .unwrap();
1059        assert_eq!(
1060            s,
1061            "{\"colors\"\t:\t[\"red\"\n,\"blue\"\n,\"taupe\"]\n,\"sub\"\t:\t{\"name\"\t:\t\"Foo\"\n,\"on\"\t:\ttrue\n,\"size\"\t:\t17}}"
1062        );
1063    }
1064
1065    #[test]
1066    fn test_format_unicode() {
1067        let value = json!({
1068            "föö": "snow☃man",
1069            "\u{1F410}": "\u{1F600}",
1070        });
1071        let s = JsonFormat::new().format_to_string(&value).unwrap();
1072        assert_eq!(s, "{\"föö\":\"snow☃man\",\"\u{1F410}\":\"\u{1F600}\"}");
1073    }
1074
1075    #[test]
1076    fn test_format_unicode_in_ascii() {
1077        let value = json!({
1078            "föö": "snow☃man",
1079            "\u{1F410}": "\u{1F600}",
1080        });
1081        let s = JsonFormat::new()
1082            .ascii(true)
1083            .format_to_string(&value)
1084            .unwrap();
1085        assert_eq!(
1086            s,
1087            r#"{"f\u00f6\u00f6":"snow\u2603man","\ud83d\udc10":"\ud83d\ude00"}"#
1088        );
1089    }
1090
1091    #[test]
1092    fn test_format_top_level_array() {
1093        let value = json!(["apple", ["banana"], {"grape": "raisin"}]);
1094        let s = JsonFormat::new().format_to_string(&value).unwrap();
1095        assert_eq!(s, r#"["apple",["banana"],{"grape":"raisin"}]"#);
1096    }
1097
1098    #[test]
1099    fn test_format_top_level_array_pretty() {
1100        let value = json!(["apple", ["banana"], {"grape": "raisin"}]);
1101        let s = JsonFormat::pretty().format_to_string(&value).unwrap();
1102        assert_eq!(
1103            s,
1104            indoc! {r#"[
1105              "apple",
1106              [
1107                "banana"
1108              ],
1109              {
1110                "grape": "raisin"
1111              }
1112            ]"#}
1113        );
1114    }
1115
1116    #[test]
1117    fn test_format_top_level_int() {
1118        let s = JsonFormat::new().format_to_string(&42).unwrap();
1119        assert_eq!(s, "42");
1120    }
1121
1122    #[test]
1123    fn test_format_top_level_int_pretty() {
1124        let s = JsonFormat::pretty().format_to_string(&42).unwrap();
1125        assert_eq!(s, "42");
1126    }
1127
1128    #[test]
1129    fn test_format_top_level_float() {
1130        let s = JsonFormat::new().format_to_string(&6.022).unwrap();
1131        assert_eq!(s, "6.022");
1132    }
1133
1134    #[test]
1135    fn test_format_top_level_float_pretty() {
1136        let s = JsonFormat::pretty().format_to_string(&6.022).unwrap();
1137        assert_eq!(s, "6.022");
1138    }
1139
1140    #[test]
1141    fn test_format_top_level_string() {
1142        let s = JsonFormat::new().format_to_string("foo").unwrap();
1143        assert_eq!(s, r#""foo""#);
1144    }
1145
1146    #[test]
1147    fn test_format_top_level_string_pretty() {
1148        let s = JsonFormat::pretty().format_to_string("foo").unwrap();
1149        assert_eq!(s, r#""foo""#);
1150    }
1151
1152    #[test]
1153    fn test_format_top_level_bool() {
1154        let s = JsonFormat::new().format_to_string(&true).unwrap();
1155        assert_eq!(s, "true");
1156    }
1157
1158    #[test]
1159    fn test_format_top_level_bool_pretty() {
1160        let s = JsonFormat::pretty().format_to_string(&true).unwrap();
1161        assert_eq!(s, "true");
1162    }
1163
1164    #[test]
1165    fn test_format_top_level_null() {
1166        let value = json!(null);
1167        let s = JsonFormat::new().format_to_string(&value).unwrap();
1168        assert_eq!(s, "null");
1169    }
1170
1171    #[test]
1172    fn test_format_top_level_null_pretty() {
1173        let value = json!(null);
1174        let s = JsonFormat::pretty().format_to_string(&value).unwrap();
1175        assert_eq!(s, "null");
1176    }
1177
1178    #[test]
1179    fn test_display_invalid_character() {
1180        let e = JsonSyntaxError::InvalidCharacter('ö');
1181        assert_eq!(e.to_string(), "string contains unexpected character 'ö'");
1182    }
1183
1184    #[test]
1185    fn test_display_missing_separator() {
1186        let e = JsonSyntaxError::MissingSeparator('?');
1187        assert_eq!(e.to_string(), "no occurrence of '?' found in string");
1188    }
1189
1190    #[test]
1191    fn test_display_multiple_separators() {
1192        let e = JsonSyntaxError::MultipleSeparators('?');
1193        assert_eq!(e.to_string(), "multiple occurrences of '?' found in string");
1194    }
1195}