buup/transformers/
json_minifier.rs1use crate::{Transform, TransformError, TransformerCategory};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct JsonMinifier;
6
7impl Transform for JsonMinifier {
8 fn name(&self) -> &'static str {
9 "JSON Minifier"
10 }
11
12 fn id(&self) -> &'static str {
13 "jsonminifier"
14 }
15
16 fn description(&self) -> &'static str {
17 "Minifies a JSON string, removing unnecessary whitespace."
18 }
19
20 fn category(&self) -> TransformerCategory {
21 TransformerCategory::Formatter
22 }
23
24 fn default_test_input(&self) -> &'static str {
25 r#"{
26 "name": "buup",
27 "version": 0.1,
28 "features": [
29 "cli",
30 "web",
31 "lib"
32 ],
33 "active": true,
34 "config": null
35}"#
36 }
37
38 fn transform(&self, input: &str) -> Result<String, TransformError> {
39 if input.trim().is_empty() {
41 return Ok(String::new());
42 }
43
44 let normalized_input = input.replace(['\u{201C}', '\u{201D}'], "\"");
46
47 minify_json(&normalized_input)
48 }
49}
50
51fn minify_json(input: &str) -> Result<String, TransformError> {
53 let mut result = String::with_capacity(input.len());
54 let chars = input.chars();
55 let mut in_string = false;
56 let mut escaped = false;
57
58 for c in chars {
59 if in_string {
60 result.push(c);
62
63 if escaped {
64 escaped = false;
66 } else if c == '\\' {
67 escaped = true;
68 } else if c == '"' {
69 in_string = false;
70 }
71 } else {
72 match c {
73 '"' => {
75 result.push(c);
76 in_string = true;
77 }
78 '{' | '}' | '[' | ']' | ':' | ',' => {
80 result.push(c);
81 }
82 ' ' | '\t' | '\n' | '\r' => {
84 }
86 '0'..='9' | '-' | '+' | '.' | 'e' | 'E' | 't' | 'f' | 'n' => {
88 result.push(c);
89 }
90 'a'..='z' | 'A'..='Z' => {
92 result.push(c);
93 }
94 _ => {
96 return Err(TransformError::JsonParseError(format!(
97 "Invalid character: '{}'",
98 c
99 )))
100 }
101 }
102 }
103 }
104
105 if in_string {
107 return Err(TransformError::JsonParseError("Unterminated string".into()));
108 }
109
110 Ok(result)
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_json_minifier_empty() {
119 let transformer = JsonMinifier;
120 assert_eq!(transformer.transform("").unwrap(), "");
121 assert_eq!(transformer.transform(" ").unwrap(), "");
122 }
123
124 #[test]
125 fn test_json_minifier_simple() {
126 let transformer = JsonMinifier;
127 let input = transformer.default_test_input();
128 let expected = r#"{"name":"buup","version":0.1,"features":["cli","web","lib"],"active":true,"config":null}"#;
129 assert_eq!(transformer.transform(input).unwrap(), expected);
130 }
131
132 #[test]
133 fn test_json_minifier_nested() {
134 let transformer = JsonMinifier;
135 let input = r#"{
136 "person": {
137 "name": "John",
138 "age": 30,
139 "address": {
140 "city": "New York",
141 "zip": "10001"
142 }
143 },
144 "active": true
145}"#;
146 let expected = r#"{"person":{"name":"John","age":30,"address":{"city":"New York","zip":"10001"}},"active":true}"#;
147 assert_eq!(transformer.transform(input).unwrap(), expected);
148 }
149
150 #[test]
151 fn test_json_minifier_array() {
152 let transformer = JsonMinifier;
153 let input = r#"[
154 1,
155 2,
156 3,
157 {
158 "name": "John"
159 }
160]"#;
161 let expected = r#"[1,2,3,{"name":"John"}]"#;
162 assert_eq!(transformer.transform(input).unwrap(), expected);
163 }
164
165 #[test]
166 fn test_json_minifier_preserve_strings() {
167 let transformer = JsonMinifier;
168 let input = r#"{
169 "text": "This has spaces and \n newlines \t tabs"
170}"#;
171 let expected = r#"{"text":"This has spaces and \n newlines \t tabs"}"#;
172 assert_eq!(transformer.transform(input).unwrap(), expected);
173 }
174
175 #[test]
176 fn test_json_minifier_smart_quotes() {
177 let transformer = JsonMinifier;
178
179 let input = r#"{"test":“value”}"#;
180 let expected = r#"{"test":"value"}"#;
181 assert_eq!(transformer.transform(input).unwrap(), expected);
182 }
183}