jsonkit/
prettify.rs

1
2// Enum for selecting indentation style
3pub enum IndentStyle {
4    Tabs,           // Use \t (Tab)
5    Spaces(usize),  // Use spaces with specified count (e.g., 2 or 4)
6}
7
8/// Function that accepts a JSON string and indentation style, 
9/// returning a prettified JSON string (Pure Rust implementation).
10pub fn prettify(raw_json: &str, style: IndentStyle) -> Result<String, String> {
11    let mut output = String::new();
12    let mut indent_level = 0;
13    let mut in_string = false;
14    let mut is_escaped = false; // Check for escaped characters like \" inside strings
15
16    // Create the string used for a single indentation unit
17    let indent_str = match style {
18        IndentStyle::Tabs => "\t".to_string(),
19        IndentStyle::Spaces(count) => " ".repeat(count),
20    };
21
22    // Helper closure to add indentation based on the current level
23    let push_indent = |buf: &mut String, level: usize| {
24        buf.push('\n');
25        for _ in 0..level {
26            buf.push_str(&indent_str);
27        }
28    };
29
30    let chars: Vec<char> = crate::strip_comments(raw_json).chars().collect();
31    
32    // Iterate through each character
33    for (_i, &c) in chars.iter().enumerate() {
34        // 1. Handle string state (to skip formatting inside text)
35        if c == '"' && !is_escaped {
36            in_string = !in_string;
37            output.push(c);
38            continue;
39        }
40
41        // Check for escape char for the next iteration (e.g., found \ and the previous wasn't \)
42        if in_string {
43            if c == '\\' && !is_escaped {
44                is_escaped = true;
45            } else {
46                is_escaped = false;
47            }
48            output.push(c);
49            continue;
50        }
51
52        // --- The logic below runs only when outside a string ---
53
54        // Skip existing whitespace (Space, Tab, Newline) to minify before reformatting
55        if c.is_whitespace() {
56            continue;
57        }
58
59        match c {
60            '{' | '[' => {
61                output.push(c);
62                indent_level += 1;
63                push_indent(&mut output, indent_level);
64            }
65            '}' | ']' => {
66                if indent_level == 0 {
67                    return Err("Unbalanced braces or brackets".to_string());
68                }
69                indent_level -= 1;
70                push_indent(&mut output, indent_level);
71                output.push(c);
72            }
73            ',' => {
74                output.push(c);
75                push_indent(&mut output, indent_level);
76            }
77            ':' => {
78                output.push(c);
79                output.push(' '); // Add space after : for better readability
80            }
81            _ => {
82                output.push(c);
83            }
84        }
85    }
86
87    // Basic Validation: If finished but still inside a string or unbalanced braces
88    if in_string {
89        return Err("Unclosed string quote".to_string());
90    }
91    if indent_level != 0 {
92        return Err("Unbalanced braces or brackets".to_string());
93    }
94
95    Ok(output)
96}
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_format_simple_json() {
103        let json = r#"{"key":"value"}"#;
104        let expected = "{\n  \"key\": \"value\"\n}";
105        assert_eq!(prettify(json, IndentStyle::Spaces(2)).unwrap(), expected);
106    }
107
108    #[test]
109    fn test_extra_closing_brace() {
110        let json = r#"{"key": "value"}}"#;
111        assert!(prettify(json, IndentStyle::Spaces(2)).is_err());
112    }
113
114
115    #[test]
116    fn test_format_nested_json() {
117        let json = r#"{"key":{"nested":"value"}}"#;
118        let expected = "{\n  \"key\": {\n    \"nested\": \"value\"\n  }\n}";
119        let result = prettify(json, IndentStyle::Spaces(2)).unwrap();
120        assert_eq!(result, expected);
121    }
122
123    #[test]
124    fn test_format_array() {
125        let json = r#"[1,2,3]"#;
126        let expected = "[\n\t1,\n\t2,\n\t3\n]";
127        let result = prettify(json, IndentStyle::Tabs).unwrap();
128        assert_eq!(result, expected);
129    }
130
131    #[test]
132    fn test_prettify_with_special_chars() {
133        let json = r#"{"key": "value: with colon, and braces {}"}"#;
134        let expected = "{\n    \"key\": \"value: with colon, and braces {}\"\n}";
135        let result = prettify(json, IndentStyle::Spaces(4)).unwrap();
136        assert_eq!(result, expected);
137    }
138
139    #[test]
140    fn test_unbalanced_braces() {
141        let json = r#"{"key":"value""#;
142        let result = prettify(json, IndentStyle::Spaces(4));
143        assert!(result.is_err());
144        assert_eq!(result.unwrap_err(), "Unbalanced braces or brackets");
145    }
146
147    #[test]
148    fn test_unclosed_string() {
149        let json = r#"{"key":"value}"#; // Missing closing quote for value
150        let result = prettify(json, IndentStyle::Spaces(4));
151        assert!(result.is_err());
152        assert_eq!(result.unwrap_err(), "Unclosed string quote");
153    }
154}