Skip to main content

rusty_rich/
json.rs

1//! JSON pretty printing — equivalent to Rich's `json.py`.
2
3use serde_json::Value;
4use crate::console::{ConsoleOptions, RenderResult, Renderable};
5use crate::segment::Segment;
6use crate::style::Style;
7use crate::theme::Theme;
8
9/// Render a JSON value with syntax highlighting.
10pub fn render_json(value: &Value) -> JsonRender {
11    JsonRender {
12        value: value.clone(),
13        indent: 2,
14        theme: None,
15        highlight: true,
16    }
17}
18
19#[derive(Debug, Clone)]
20pub struct JsonRender {
21    value: Value,
22    indent: usize,
23    theme: Option<Theme>,
24    highlight: bool,
25}
26
27impl JsonRender {
28    pub fn indent(mut self, n: usize) -> Self { self.indent = n; self }
29    pub fn theme(mut self, t: Theme) -> Self { self.theme = Some(t); self }
30
31    fn style_for(&self, name: &str) -> Style {
32        if let Some(ref t) = self.theme {
33            t.get(name).cloned().unwrap_or(Style::new())
34        } else {
35            
36            let theme = crate::theme::default_theme();
37            theme.get(name).cloned().unwrap_or(Style::new())
38        }
39    }
40}
41
42impl Renderable for JsonRender {
43    fn render(&self, _options: &ConsoleOptions) -> RenderResult {
44        let formatted = format_json_value(
45            &self.value,
46            self.indent,
47            0,
48            self.highlight,
49            &|name| self.style_for(name),
50        );
51        let lines: Vec<Vec<Segment>> = formatted
52            .lines()
53            .map(|line| vec![Segment::new(line)])
54            .collect();
55        RenderResult { lines, items: Vec::new() }
56    }
57}
58
59fn format_json_value(
60    value: &Value,
61    indent: usize,
62    level: usize,
63    highlight: bool,
64    get_style: &dyn Fn(&str) -> Style,
65) -> String {
66    if !highlight {
67        return serde_json::to_string_pretty(value).unwrap_or_else(|_| format!("{value:?}"));
68    }
69
70    match value {
71        Value::Null => {
72            let s = get_style("json.null");
73            format!("{}{}null{}", s.to_ansi(), s.reset_ansi(), "")
74        }
75        Value::Bool(b) => {
76            let s = get_style("json.bool");
77            format!("{}{b}{}", s.to_ansi(), s.reset_ansi())
78        }
79        Value::Number(n) => {
80            let s = get_style("json.number");
81            format!("{}{n}{}", s.to_ansi(), s.reset_ansi())
82        }
83        Value::String(s) => {
84            let st = get_style("json.str");
85            format!("{}\"{}\"{}", st.to_ansi(), s.escape_default(), st.reset_ansi())
86        }
87        Value::Array(arr) => {
88            if arr.is_empty() {
89                return "[]".to_string();
90            }
91            let brace = get_style("json.brace");
92            let mut out = format!("{}[{}", brace.to_ansi(), brace.reset_ansi());
93            let pad = " ".repeat(indent * (level + 1));
94            let close_pad = " ".repeat(indent * level);
95
96            for (i, item) in arr.iter().enumerate() {
97                let item_str = format_json_value(item, indent, level + 1, highlight, get_style);
98                out.push('\n');
99                out.push_str(&pad);
100                out.push_str(&item_str);
101                if i < arr.len() - 1 {
102                    out.push(',');
103                }
104            }
105            out.push('\n');
106            out.push_str(&close_pad);
107            out.push_str(&format!("{}]{}", brace.to_ansi(), brace.reset_ansi()));
108            out
109        }
110        Value::Object(obj) => {
111            if obj.is_empty() {
112                return "{}".to_string();
113            }
114            let brace = get_style("json.brace");
115            let key_style = get_style("json.key");
116            let mut out = format!("{}{{{}", brace.to_ansi(), brace.reset_ansi());
117            let pad = " ".repeat(indent * (level + 1));
118            let close_pad = " ".repeat(indent * level);
119            let keys: Vec<&String> = obj.keys().collect();
120
121            for (i, key) in keys.iter().enumerate() {
122                let val = &obj[*key];
123                let val_str = format_json_value(val, indent, level + 1, highlight, get_style);
124                out.push('\n');
125                out.push_str(&pad);
126                out.push_str(&format!(
127                    "{}\"{}\"{}: ",
128                    key_style.to_ansi(),
129                    key.escape_default(),
130                    key_style.reset_ansi()
131                ));
132                out.push_str(&val_str);
133                if i < keys.len() - 1 {
134                    out.push(',');
135                }
136            }
137            out.push('\n');
138            out.push_str(&close_pad);
139            out.push_str(&format!("{}}}{}", brace.to_ansi(), brace.reset_ansi()));
140            out
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_format_json() {
151        let v: Value = serde_json::from_str(r#"{"name": "Alice", "age": 30}"#).unwrap();
152        let rendered = render_json(&v);
153        let result = rendered.render(&ConsoleOptions::default());
154        assert!(result.to_ansi().contains("Alice"));
155    }
156}