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 =
35 serde_json::to_string_pretty(&value).unwrap_or_else(|_| json_str.to_string());
36 colorize_json(&pretty, &colors)
37 }
38 Err(_) => {
39 json_str.to_string()
41 }
42 }
43}
44
45fn colorize_json(json: &str, c: &JsonColors) -> String {
47 let mut out = String::with_capacity(json.len() + 256);
48 let mut in_string = false;
49 let mut after_colon = false;
50 let bytes = json.as_bytes();
51 let mut i = 0;
52
53 while i < bytes.len() {
54 let ch = bytes[i] as char;
55
56 match ch {
57 '"' if !in_string => {
58 in_string = true;
59 let rest = &json[i + 1..];
61 let key_end = rest.find('"').unwrap_or(rest.len());
62 let after_key = &rest[key_end + 1..];
63 let is_key = after_key.trim_start().starts_with(':');
64 if is_key {
65 out.push_str(c.key);
66 } else {
67 out.push_str(c.string);
68 }
69 out.push('"');
70 }
71 '"' if in_string => {
72 out.push('"');
73 out.push_str(c.reset);
74 in_string = false;
75 after_colon = false;
76 }
77 ':' if !in_string => {
78 out.push(':');
79 out.push(' ');
80 after_colon = true;
81 }
82 't' | 'f' if !in_string && !after_colon => {
83 out.push_str(c.boolean);
84 out.push(ch);
85 }
86 'n' if !in_string && !after_colon => {
87 out.push_str(c.null);
88 out.push(ch);
89 }
90 '0'..='9' | '-' if !in_string && after_colon => {
91 out.push_str(c.number);
92 out.push(ch);
93 after_colon = false;
94 }
95 '{' | '}' | '[' | ']' if !in_string => {
96 out.push_str(c.bracket);
97 out.push(ch);
98 out.push_str(c.reset);
99 after_colon = false;
100 }
101 _ => {
102 out.push(ch);
103 if ch == ',' && !in_string {
104 after_colon = false;
105 }
106 if ch == '\n' {
107 after_colon = false;
108 }
109 }
110 }
111 i += 1;
112 }
113
114 if in_string {
115 out.push_str(c.reset);
116 }
117 out
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn test_basic_json() {
126 let input = r#"{"name":"test","count":42,"active":true}"#;
127 let output = format_json(input);
128 assert!(output.contains("name"));
129 assert!(output.contains("42"));
130 }
131}