1use serde_json::Value;
15
16use crate::console::{Console, ConsoleOptions};
17use crate::errors::{Result, RichError};
18use crate::protocol::Renderable;
19use crate::segment::Segment;
20use crate::style::Style;
21
22pub struct Json {
24 value: Value,
25 styles: JsonStyles,
26}
27
28struct JsonStyles {
29 brace: Style,
30 key: Style,
31 string: Style,
32 number: Style,
33 bool_true: Style,
34 bool_false: Style,
35 null: Style,
36}
37
38impl JsonStyles {
39 fn defaults() -> Self {
40 let s = |spec: &str| Style::parse(spec).expect("valid built-in style");
41 JsonStyles {
42 brace: s("bold"),
43 key: s("bold blue"),
44 string: s("green"),
45 number: s("bold cyan"),
46 bool_true: s("italic bright_green"),
47 bool_false: s("italic bright_red"),
48 null: s("italic magenta"),
49 }
50 }
51}
52
53impl Json {
54 pub fn new(text: &str) -> Result<Self> {
56 let value = serde_json::from_str(text).map_err(|e| RichError::Json(e.to_string()))?;
57 Ok(Json {
58 value,
59 styles: JsonStyles::defaults(),
60 })
61 }
62
63 fn render_value(&self, value: &Value, level: usize, out: &mut Vec<Segment>) {
64 let brace = |text: &str| Segment::new(text.to_string(), Some(self.styles.brace.clone()));
65 let plain = |text: String| Segment::new(text, None);
66 match value {
67 Value::Null => out.push(Segment::new(
68 "null".to_string(),
69 Some(self.styles.null.clone()),
70 )),
71 Value::Bool(true) => out.push(Segment::new(
72 "true".to_string(),
73 Some(self.styles.bool_true.clone()),
74 )),
75 Value::Bool(false) => out.push(Segment::new(
76 "false".to_string(),
77 Some(self.styles.bool_false.clone()),
78 )),
79 Value::Number(number) => out.push(Segment::new(
80 number.to_string(),
81 Some(self.styles.number.clone()),
82 )),
83 Value::String(string) => out.push(Segment::new(
84 quote(string),
85 Some(self.styles.string.clone()),
86 )),
87 Value::Array(items) => {
88 out.push(brace("["));
89 if items.is_empty() {
90 out.push(brace("]"));
91 return;
92 }
93 out.push(plain("\n".to_string()));
94 let last = items.len() - 1;
95 for (index, item) in items.iter().enumerate() {
96 out.push(plain(" ".repeat(level + 1)));
97 self.render_value(item, level + 1, out);
98 if index != last {
99 out.push(plain(",".to_string()));
100 }
101 out.push(plain("\n".to_string()));
102 }
103 out.push(plain(" ".repeat(level)));
104 out.push(brace("]"));
105 }
106 Value::Object(map) => {
107 out.push(brace("{"));
108 if map.is_empty() {
109 out.push(brace("}"));
110 return;
111 }
112 out.push(plain("\n".to_string()));
113 let last = map.len() - 1;
114 for (index, (key, item)) in map.iter().enumerate() {
115 out.push(plain(" ".repeat(level + 1)));
116 out.push(Segment::new(quote(key), Some(self.styles.key.clone())));
117 out.push(plain(": ".to_string()));
118 self.render_value(item, level + 1, out);
119 if index != last {
120 out.push(plain(",".to_string()));
121 }
122 out.push(plain("\n".to_string()));
123 }
124 out.push(plain(" ".repeat(level)));
125 out.push(brace("}"));
126 }
127 }
128 }
129}
130
131impl Renderable for Json {
132 fn rich_render(&self, _console: &Console, _options: &ConsoleOptions) -> Vec<Segment> {
133 let mut segments = Vec::new();
134 self.render_value(&self.value, 0, &mut segments);
135 segments
136 }
137}
138
139fn quote(string: &str) -> String {
141 serde_json::to_string(string).unwrap_or_else(|_| format!("{string:?}"))
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::color::ColorSystem;
148
149 fn render(text: &str) -> String {
150 let console = Console::builder()
151 .force_terminal(true)
152 .color_system(Some(ColorSystem::Truecolor))
153 .width(40)
154 .build();
155 console.render_to_string(&Json::new(text).unwrap())
156 }
157
158 #[test]
159 fn empty_collections_stay_inline() {
160 assert_eq!(render("{}"), "\x1b[1m{\x1b[0m\x1b[1m}\x1b[0m");
161 assert_eq!(render("[]"), "\x1b[1m[\x1b[0m\x1b[1m]\x1b[0m");
162 }
163
164 #[test]
165 fn object_with_scalars() {
166 assert_eq!(
167 render(r#"{"ok": false}"#),
168 "\x1b[1m{\x1b[0m\n \x1b[1;34m\"ok\"\x1b[0m: \x1b[3;91mfalse\x1b[0m\n\x1b[1m}\x1b[0m"
169 );
170 }
171
172 #[test]
173 fn invalid_json_errors() {
174 assert!(Json::new("{not json}").is_err());
175 }
176
177 #[test]
178 fn non_ascii_stays_utf8_in_input_order() {
179 let out = render("{\"name\": \"caf\u{e9}\", \"emoji\": \"\u{2764}\"}");
183 assert!(out.contains("caf\u{e9}"), "café stays UTF-8: {out:?}");
184 assert!(out.contains('\u{2764}'), "heart stays UTF-8");
185 let name_at = out.find("name").expect("name key present");
186 let emoji_at = out.find("emoji").expect("emoji key present");
187 assert!(name_at < emoji_at, "keys keep input order");
188 }
189}