rill_json/value.rs
1//! Contains the `JsonValue` enum, a native Rust representation of any
2//! valid JSON value.
3//!
4//! This module also includes the "stringify" (serialization) logic
5//! for converting a `JsonValue` back into a JSON string.
6use std::collections::HashMap;
7use std::fmt;
8
9// --- 5. JSON Value Enum (for Stage 16) ---
10
11/// A native Rust representation of any valid JSON value.
12///
13/// This enum is used by the `stringify` functions to serialize
14/// Rust data *into* a JSON string.
15#[derive(Debug, PartialEq, Clone)]
16pub enum JsonValue {
17 /// Represents a JSON `null`.
18 Null,
19 /// Represents a JSON `true` or `false`.
20 Boolean(bool),
21 /// Represents a JSON number (stored as `f64`).
22 Number(f64),
23 /// Represents a JSON string.
24 String(String),
25 /// Represents a JSON array (list).
26 Array(Vec<JsonValue>),
27 /// Represents a JSON object (map).
28 Object(HashMap<String, JsonValue>),
29}
30
31// --- 7. Stringify (Serialization - Stage 16) ---
32impl JsonValue {
33 /// Serializes the `JsonValue` into a compact, minified JSON string.
34 ///
35 /// # Examples
36 /// ```
37 /// use rill_json::JsonValue;
38 /// let val = JsonValue::Number(123.0);
39 /// assert_eq!(val.stringify(), "123");
40 /// ```
41 pub fn stringify(&self) -> String {
42 let mut output = String::new();
43 // This unwrap is safe because writing to a String never fails.
44 Self::write_value(self, &mut output).unwrap();
45 output
46 }
47
48 /// Recursive helper function to write any `JsonValue` to a string buffer.
49 fn write_value<W: fmt::Write>(value: &JsonValue, w: &mut W) -> fmt::Result {
50 match value {
51 JsonValue::Null => w.write_str("null"),
52 JsonValue::Boolean(b) => w.write_str(if *b { "true" } else { "false" }),
53 JsonValue::Number(n) => write!(w, "{}", n),
54 JsonValue::String(s) => Self::write_string(s, w),
55 JsonValue::Array(a) => Self::write_array(a, w),
56 JsonValue::Object(o) => Self::write_object(o, w),
57 }
58 }
59
60 /// Helper to write a JSON array (compact).
61 fn write_array<W: fmt::Write>(arr: &Vec<JsonValue>, w: &mut W) -> fmt::Result {
62 w.write_char('[')?;
63 let mut first = true;
64 for val in arr {
65 if !first {
66 w.write_char(',')?;
67 }
68 Self::write_value(val, w)?;
69 first = false;
70 }
71 w.write_char(']')
72 }
73
74 /// Helper to write a JSON object (compact).
75 fn write_object<W: fmt::Write>(obj: &HashMap<String, JsonValue>, w: &mut W) -> fmt::Result {
76 w.write_char('{')?;
77 let mut first = true;
78 // Note: HashMap iteration order is not guaranteed,
79 // but this is fine according to the JSON specification.
80 for (key, val) in obj {
81 if !first {
82 w.write_char(',')?;
83 }
84 Self::write_string(key, w)?; // Write the key (which must be a string)
85 w.write_char(':')?;
86 Self::write_value(val, w)?; // Write the value
87 first = false;
88 }
89 w.write_char('}')
90 }
91
92 /// Helper to write an escaped JSON string.
93 /// This handles all required JSON escape sequences (e.g., `\"`, `\\`, `\n`).
94 fn write_string<W: fmt::Write>(s: &str, w: &mut W) -> fmt::Result {
95 w.write_char('"')?;
96 for c in s.chars() {
97 match c {
98 // Standard escapes
99 '"' => w.write_str("\\\""),
100 '\\' => w.write_str("\\\\"),
101 '/' => w.write_str("\\/"), // Optional, but good practice
102 '\u{0008}' => w.write_str("\\b"), // Backspace
103 '\u{000C}' => w.write_str("\\f"), // Form feed
104 '\n' => w.write_str("\\n"), // Newline
105 '\r' => w.write_str("\\r"), // Carriage return
106 '\t' => w.write_str("\\t"), // Tab
107 // Control characters must be escaped as \uXXXX
108 '\u{0000}'..='\u{001F}' => {
109 write!(w, "\\u{:04x}", c as u32)
110 }
111 _ => w.write_char(c),
112 }?;
113 }
114 w.write_char('"')
115 }
116
117 // --- Pretty Print Bonus ---
118
119 /// The indentation string to use for pretty-printing (two spaces).
120 const INDENT: &'static str = " ";
121
122 /// Serializes the `JsonValue` into a human-readable,
123 /// indented JSON string ("pretty-print").
124 ///
125 /// # Examples
126 /// ```
127 /// use rill_json::JsonValue;
128 /// use std::collections::HashMap;
129 ///
130 /// let mut obj = HashMap::new();
131 /// obj.insert("key".to_string(), JsonValue::String("value".to_string()));
132 /// let val = JsonValue::Object(obj);
133 ///
134 /// let pretty = val.stringify_pretty();
135 /// assert!(pretty.starts_with("{\n"));
136 /// assert!(pretty.contains("\n \"key\": \"value\"\n"));
137 /// assert!(pretty.ends_with("\n}"));
138 /// ```
139 pub fn stringify_pretty(&self) -> String {
140 let mut output = String::new();
141 // This unwrap is safe because writing to a String never fails.
142 Self::write_value_pretty(self, &mut output, 0).unwrap();
143 output
144 }
145
146 /// Recursive helper for pretty-printing a value.
147 fn write_value_pretty<W: fmt::Write>(
148 value: &JsonValue,
149 w: &mut W,
150 depth: usize,
151 ) -> fmt::Result {
152 match value {
153 // Primitives are written the same as compact
154 JsonValue::Null => w.write_str("null"),
155 JsonValue::Boolean(b) => w.write_str(if *b { "true" } else { "false" }),
156 JsonValue::Number(n) => write!(w, "{}", n),
157 JsonValue::String(s) => Self::write_string(s, w),
158 // Composites (Array/Object) get new logic
159 JsonValue::Array(a) => Self::write_array_pretty(a, w, depth),
160 JsonValue::Object(o) => Self::write_object_pretty(o, w, depth),
161 }
162 }
163
164 /// Helper to pretty-print a JSON array.
165 fn write_array_pretty<W: fmt::Write>(
166 arr: &Vec<JsonValue>,
167 w: &mut W,
168 depth: usize,
169 ) -> fmt::Result {
170 // Empty array is just "[]"
171 if arr.is_empty() {
172 return w.write_str("[]");
173 }
174
175 let new_depth = depth + 1;
176 let indent = Self::INDENT.repeat(new_depth);
177 let closing_indent = Self::INDENT.repeat(depth);
178
179 w.write_str("[\n")?; // Opening bracket and newline
180
181 let mut first = true;
182 for val in arr {
183 if !first {
184 w.write_str(",\n")?; // Comma and newline before next item
185 }
186 w.write_str(&indent)?; // Indent
187 Self::write_value_pretty(val, w, new_depth)?; // Write the value
188 first = false;
189 }
190
191 write!(w, "\n{}", closing_indent)?; // Newline and closing indent
192 w.write_char(']') // Closing bracket
193 }
194
195 /// Helper to pretty-print a JSON object.
196 fn write_object_pretty<W: fmt::Write>(
197 obj: &HashMap<String, JsonValue>,
198 w: &mut W,
199 depth: usize,
200 ) -> fmt::Result {
201 // Empty object is just "{}"
202 if obj.is_empty() {
203 return w.write_str("{}");
204 }
205
206 let new_depth = depth + 1;
207 let indent = Self::INDENT.repeat(new_depth);
208 let closing_indent = Self::INDENT.repeat(depth);
209
210 w.write_str("{\n")?; // Opening brace and newline
211
212 let mut first = true;
213 for (key, val) in obj {
214 if !first {
215 w.write_str(",\n")?; // Comma and newline before next item
216 }
217 w.write_str(&indent)?; // Indent
218 Self::write_string(key, w)?; // Write the key
219 w.write_str(": ")?; // Colon and space
220 Self::write_value_pretty(val, w, new_depth)?; // Write the value
221 first = false;
222 }
223
224 write!(w, "\n{}", closing_indent)?; // Newline and closing indent
225 w.write_char('}') // Closing brace
226 }
227}