sparrow/tui/formatters/
json.rs1pub struct JsonColors {
5 pub key: &'static str,
6 pub string: &'static str,
7 pub number: &'static str,
8 pub boolean: &'static str,
9 pub null: &'static str,
10 pub bracket: &'static str,
11 pub reset: &'static str,
12}
13
14impl Default for JsonColors {
15 fn default() -> Self {
16 Self {
17 key: "\x1b[38;2;111;166;230m",
18 string: "\x1b[38;2;116;194;88m",
19 number: "\x1b[38;2;242;201;76m",
20 boolean: "\x1b[38;2;86;182;194m",
21 null: "\x1b[38;2;137;125;108m",
22 bracket: "\x1b[38;2;185;176;163m",
23 reset: "\x1b[0m",
24 }
25 }
26}
27
28pub fn format_json(json_str: &str) -> String {
30 let colors = JsonColors::default();
31 match serde_json::from_str::<serde_json::Value>(json_str) {
33 Ok(value) => {
34 let pretty = serde_json::to_string_pretty(&value).unwrap_or_else(|_| json_str.to_string());
35 colorize_json(&pretty, &colors)
36 }
37 Err(_) => {
38 json_str.to_string()
40 }
41 }
42}
43
44fn colorize_json(json: &str, c: &JsonColors) -> String {
46 let mut out = String::with_capacity(json.len() + 256);
47 let mut in_string = false;
48 let mut after_colon = false;
49 let bytes = json.as_bytes();
50 let mut i = 0;
51
52 while i < bytes.len() {
53 let ch = bytes[i] as char;
54
55 match ch {
56 '"' if !in_string => {
57 in_string = true;
58 let rest = &json[i + 1..];
60 let key_end = rest.find('"').unwrap_or(rest.len());
61 let after_key = &rest[key_end + 1..];
62 let is_key = after_key.trim_start().starts_with(':');
63 if is_key {
64 out.push_str(c.key);
65 } else {
66 out.push_str(c.string);
67 }
68 out.push('"');
69 }
70 '"' if in_string => {
71 out.push('"');
72 out.push_str(c.reset);
73 in_string = false;
74 after_colon = false;
75 }
76 ':' if !in_string => {
77 out.push(':');
78 out.push(' ');
79 after_colon = true;
80 }
81 't' | 'f' if !in_string && !after_colon => {
82 out.push_str(c.boolean);
83 out.push(ch);
84 }
85 'n' if !in_string && !after_colon => {
86 out.push_str(c.null);
87 out.push(ch);
88 }
89 '0'..='9' | '-' if !in_string && after_colon => {
90 out.push_str(c.number);
91 out.push(ch);
92 after_colon = false;
93 }
94 '{' | '}' | '[' | ']' if !in_string => {
95 out.push_str(c.bracket);
96 out.push(ch);
97 out.push_str(c.reset);
98 after_colon = false;
99 }
100 _ => {
101 out.push(ch);
102 if ch == ',' && !in_string {
103 after_colon = false;
104 }
105 if ch == '\n' {
106 after_colon = false;
107 }
108 }
109 }
110 i += 1;
111 }
112
113 if in_string {
114 out.push_str(c.reset);
115 }
116 out
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn test_basic_json() {
125 let input = r#"{"name":"test","count":42,"active":true}"#;
126 let output = format_json(input);
127 assert!(output.contains("name"));
128 assert!(output.contains("42"));
129 }
130}