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