serde_json_fmt/
lib.rs

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