Skip to main content

df_hyperliquid_core/utils/json/
write.rs

1//! Writing JSON: one line as `JSON.stringify` writes it, or two spaces a level for a person.
2
3use std::fmt::Write as _;
4
5use crate::utils::json::Json;
6
7impl Json {
8    /// One line, no spaces: what `JSON.stringify` writes, byte for byte.
9    #[must_use]
10    pub fn compact(&self) -> String {
11        let mut out = String::new();
12        self.write(&mut out, None, 0);
13        out
14    }
15
16    /// Two spaces a level, for showing a body to a person.
17    #[must_use]
18    pub fn pretty(&self) -> String {
19        let mut out = String::new();
20        self.write(&mut out, Some(2), 0);
21        out
22    }
23
24    fn write(&self, out: &mut String, indent: Option<usize>, depth: usize) {
25        let open = |out: &mut String, depth: usize| {
26            if let Some(step) = indent {
27                out.push('\n');
28                out.push_str(&" ".repeat(step * depth));
29            }
30        };
31        match self {
32            Json::Null => out.push_str("null"),
33            Json::Bool(value) => out.push_str(if *value { "true" } else { "false" }),
34            Json::Number(digits) => out.push_str(digits),
35            Json::String(text) => quote(text, out),
36            Json::Array(items) if items.is_empty() => out.push_str("[]"),
37            Json::Array(items) => {
38                out.push('[');
39                for (at, item) in items.iter().enumerate() {
40                    if at > 0 {
41                        out.push(',');
42                    }
43                    open(out, depth + 1);
44                    item.write(out, indent, depth + 1);
45                }
46                open(out, depth);
47                out.push(']');
48            }
49            Json::Object(entries) if entries.is_empty() => out.push_str("{}"),
50            Json::Object(entries) => {
51                out.push('{');
52                for (at, (key, value)) in entries.iter().enumerate() {
53                    if at > 0 {
54                        out.push(',');
55                    }
56                    open(out, depth + 1);
57                    quote(key, out);
58                    out.push(':');
59                    if indent.is_some() {
60                        out.push(' ');
61                    }
62                    value.write(out, indent, depth + 1);
63                }
64                open(out, depth);
65                out.push('}');
66            }
67        }
68    }
69}
70
71fn quote(text: &str, out: &mut String) {
72    out.push('"');
73    for letter in text.chars() {
74        match letter {
75            '"' => out.push_str("\\\""),
76            '\\' => out.push_str("\\\\"),
77            '\n' => out.push_str("\\n"),
78            '\r' => out.push_str("\\r"),
79            '\t' => out.push_str("\\t"),
80            '\u{08}' => out.push_str("\\b"),
81            '\u{0c}' => out.push_str("\\f"),
82            control if u32::from(control) < 0x20 => {
83                let _ = write!(out, "\\u{:04x}", u32::from(control));
84            }
85            other => out.push(other),
86        }
87    }
88    out.push('"');
89}
90
91#[cfg(test)]
92#[path = "tests/write.rs"]
93mod tests;