jaq_json/
write.rs

1//! Functions and macros for writing (parts of) values.
2//!
3//! We use macros so that we can create both
4//! formatters ([core::fmt::Formatter]) and
5//! writers ([std::io::Write]) from the same code.
6
7use crate::{Tag, Val};
8use alloc::string::String;
9use core::fmt::{self, Formatter};
10use std::io::{self, Write};
11
12/// Write a byte.
13///
14/// This uses `$f` to write bytes not corresponding to normal ASCII characters.
15///
16/// This is especially useful to pretty-print control characters, such as
17/// `'\n'` (U+000A), but also all other control characters.
18#[macro_export]
19macro_rules! write_byte {
20    ($w:ident, $c:expr, $f:expr) => {{
21        match $c {
22            // Rust does not recognise the following two character escapes
23            0x08 => write!($w, "\\b"),
24            0x0c => write!($w, "\\f"),
25            c @ (b'\t' | b'\n' | b'\r' | b'\\' | b'"') => {
26                write!($w, "{}", char::from(c).escape_default())
27            }
28            0x00..=0x1F | 0x7F..=0xFF => $f,
29            c => write!($w, "{}", char::from(c)),
30        }
31    }};
32}
33
34/// Write a UTF-8 string as JSON string, including leading and trailing quotes.
35///
36/// This uses `$f` to format byte slices that do not need to be escaped.
37#[macro_export]
38macro_rules! write_utf8 {
39    ($w:ident, $s:ident, $f:expr) => {{
40        write!($w, "\"")?;
41        let is_special = |c| matches!(c, 0x00..=0x1F | b'\\' | b'"' | 0x7F);
42        for s in $s.split_inclusive(|c| is_special(*c)) {
43            match s.split_last() {
44                Some((last, init)) if is_special(*last) => {
45                    $f(init)?;
46                    $crate::write_byte!($w, *last, write!($w, "\\u{last:04x}"))?
47                }
48                _ => $f(s)?,
49            }
50        }
51        write!($w, "\"")
52    }};
53}
54
55/// Write a byte string, including leading and trailing quotes.
56///
57/// This maps all non-ASCII `u8`s to `\xXX`.
58#[macro_export]
59macro_rules! write_bytes {
60    ($w:ident, $s:ident) => {{
61        write!($w, "b\"")?;
62        $s.iter()
63            .try_for_each(|c| $crate::write_byte!($w, *c, write!($w, "\\x{c:02x}")))?;
64        write!($w, "\"")
65    }};
66}
67
68macro_rules! write_seq {
69    ($w:ident, $pp:expr, $level:expr, $xs:expr, $f:expr) => {{
70        let indent = &$pp.indent;
71        if indent.is_some() {
72            writeln!($w)?;
73        }
74        let mut iter = $xs.into_iter().peekable();
75        while let Some(x) = iter.next() {
76            if let Some(indent) = indent {
77                write!($w, "{}", indent.repeat($level + 1))?;
78            }
79            $f(x)?;
80            if iter.peek().is_some() {
81                write!($w, ",")?;
82                if $pp.sep_space && indent.is_none() {
83                    write!($w, " ")?
84                }
85            }
86            if indent.is_some() {
87                writeln!($w)?
88            }
89        }
90        if let Some(indent) = indent {
91            write!($w, "{}", indent.repeat($level))
92        } else {
93            Ok(())
94        }
95    }};
96}
97
98/// Colors used to pretty-print values.
99#[derive(Clone, Default)]
100pub struct Colors<S = String> {
101    /// null
102    pub null: S,
103    /// false
104    pub r#false: S,
105    /// true
106    pub r#true: S,
107    /// numbers
108    pub num: S,
109    /// strings
110    pub str: S,
111    /// arrays
112    pub arr: S,
113    /// objects
114    pub obj: S,
115
116    /// byte strings
117    pub bstr: S,
118
119    /// reset pretty printer
120    pub reset: S,
121}
122
123impl Colors {
124    /// Default ANSI colors
125    pub fn ansi() -> Self {
126        let mut cols = Colors::default().parse("90:39:39:39:32:1;39:1;39");
127        cols.bstr = "\x1b[31m".into();
128        cols.reset = "\x1b[0m".into();
129        cols
130    }
131
132    /// Overwrite colors with those present in `JQ_COLORS` environment variable.
133    pub fn parse(mut self, s: &str) -> Self {
134        let fields = [
135            &mut self.null,
136            &mut self.r#false,
137            &mut self.r#true,
138            &mut self.num,
139            &mut self.str,
140            &mut self.arr,
141            &mut self.obj,
142        ];
143        for (style, field) in s.split(':').zip(fields) {
144            *field = alloc::format!("\x1b[{style}m");
145        }
146        self
147    }
148}
149
150/// Pretty printer.
151#[derive(Clone, Default)]
152pub struct Pp<S = String> {
153    /// indent by repeating given string `n` times
154    pub indent: Option<S>,
155    /// sort objects by keys
156    pub sort_keys: bool,
157    /// colors for different types of values
158    pub colors: Colors<S>,
159    /// put a space after ':'
160    ///
161    /// This is necessary for YAML, which interprets
162    /// {1:2}  as {"1:2": null}, whereas it interprets
163    /// {1: 2} as {1: 2}.
164    pub sep_space: bool,
165}
166
167impl Pp {
168    /// Write a string with color.
169    pub fn write_str(
170        &self,
171        w: &mut dyn Write,
172        f: impl FnOnce(&mut dyn Write) -> io::Result<()>,
173    ) -> io::Result<()> {
174        write!(w, "{}", self.colors.str)?;
175        f(w)?;
176        write!(w, "{}", self.colors.reset)
177    }
178}
179
180/// Write a value as JSON superset, using a function `$f` to write sub-values.
181///
182/// This macro writes strings by replacing invalid UTF-8 characters with the
183/// Unicode replacement character.
184/// That way, this macro can be used not only for writers, but also for
185/// formatters, which require all output to be valid UTF-8.
186/// However, the JSON/YAML writers usually override this behaviour,
187/// yielding invalid UTF-8 characters as-is.
188macro_rules! write_val {
189    ($w:ident, $pp:ident, $level:expr, $v:ident, $f:expr) => {{
190        macro_rules! color {
191            ($style:ident, $g:expr) => {{
192                write!($w, "{}", $pp.colors.$style)?;
193                $g?;
194                write!($w, "{}", $pp.colors.reset)
195            }};
196        }
197        match $v {
198            Val::Null => color!(null, write!($w, "null")),
199            Val::Bool(true) => color!(r#true, write!($w, "true")),
200            Val::Bool(false) => color!(r#false, write!($w, "false")),
201            Val::Num(n) => color!(num, write!($w, "{n}")),
202            Val::Str(b, $crate::Tag::Bytes) => color!(bstr, $crate::write_bytes!($w, b)),
203            Val::Str(s, $crate::Tag::Utf8) => color!(
204                str,
205                write_utf8!($w, s, |part| write!($w, "{}", $crate::bstr(part)))
206            ),
207            Val::Arr(a) => {
208                color!(arr, write!($w, "["))?;
209                if !a.is_empty() {
210                    write_seq!($w, $pp, $level, &**a, |x| $f($level + 1, x))?;
211                }
212                color!(arr, write!($w, "]"))
213            }
214            Val::Obj(o) => {
215                color!(obj, write!($w, "{{"))?;
216                macro_rules! kv {
217                    ($kv:expr) => {{
218                        let (k, v) = $kv;
219                        $f($level + 1, k)?;
220                        color!(obj, write!($w, ":"))?;
221                        if $pp.sep_space {
222                            write!($w, " ")?;
223                        }
224                        $f($level + 1, v)
225                    }};
226                }
227                if !o.is_empty() {
228                    if $pp.sort_keys {
229                        let mut o: alloc::vec::Vec<_> = o.iter().collect();
230                        o.sort_by_key(|(k, _v)| *k);
231                        write_seq!($w, $pp, $level, o, |x| kv!(x))
232                    } else {
233                        write_seq!($w, $pp, $level, &**o, |x| kv!(x))
234                    }?
235                }
236                color!(obj, write!($w, "}}"))
237            }
238        }
239    }};
240}
241
242type WriteFn = fn(&mut dyn Write, &Pp, usize, &Val) -> io::Result<()>;
243type FormatFn = fn(&mut Formatter, &Pp, usize, &Val) -> fmt::Result;
244
245/// Write a value as JSON, using a custom function for child values.
246pub fn write_with(w: &mut dyn Write, pp: &Pp, level: usize, v: &Val, f: WriteFn) -> io::Result<()> {
247    match v {
248        Val::Str(s, Tag::Utf8) => pp.write_str(w, |w| write_utf8!(w, s, |part| w.write_all(part))),
249        _ => write_val!(w, pp, level, v, |level, x| f(w, pp, level, x)),
250    }
251}
252
253/// Format a value as JSON, using a custom function for child values.
254///
255/// This is useful to override how certain values are printed, e.g. for YAML.
256pub fn format_with(w: &mut Formatter, pp: &Pp, level: usize, v: &Val, f: FormatFn) -> fmt::Result {
257    write_val!(w, pp, level, v, |level, x| f(w, pp, level, x))
258}
259
260/// Write a value as JSON.
261///
262/// Note that unlike jq, this may actually produce invalid JSON.
263/// In particular, this may yield:
264///
265/// - literals for special floating-point values (NaN, Infinity, -Infinity)
266/// - invalid UTF-8 characters
267/// - byte strings with `\xXX` sequences
268/// - objects with non-string keys
269///
270/// The key principles behind this behaviour are:
271///
272/// 1. Printing a value should always succeed.
273///    (Otherwise, there would exist values that we could not even inspect.)
274/// 2. Printing a value should yield valid JSON if and only if
275///    the value can be represented by an equivalent JSON value.
276///    (To give users a chance to find non-JSON values and to take appropriate action.)
277///
278/// jq and jaq agree on principle 1, but disagree on principle 2.
279/// In particular, this shows by the fact that `jq -n 'nan'` yields `null`.
280/// That means that jq maps values that cannot be represented by JSON
281/// to different values that can be represented by JSON.
282///
283/// In summary,
284/// jq may cause silent information loss, whereas
285/// jaq may yield invalid JSON values.
286/// Choose your poison.
287pub fn write(w: &mut dyn Write, pp: &Pp, level: usize, v: &Val) -> io::Result<()> {
288    write_with(w, pp, level, v, write)
289}
290
291pub(crate) fn format(w: &mut Formatter, pp: &Pp, level: usize, v: &Val) -> fmt::Result {
292    format_with(w, pp, level, v, format)
293}