1use std::collections::HashMap;
2
3#[derive(Debug, Clone, PartialEq)]
4pub enum JsonValue {
5 Null,
6 Bool(bool),
7 Number(f64),
8 String(String),
9 Array(Vec<JsonValue>),
10 Object(HashMap<String, JsonValue>),
11}
12
13impl JsonValue {
14 pub fn is_truthy(&self) -> bool {
15 match self {
16 JsonValue::Null => false,
17 JsonValue::Bool(b) => *b,
18 JsonValue::Number(n) => *n != 0.0,
19 JsonValue::String(s) => !s.is_empty(),
20 JsonValue::Array(a) => !a.is_empty(),
21 JsonValue::Object(_) => true,
22 }
23 }
24
25 pub fn to_display_string(&self) -> String {
26 match self {
27 JsonValue::Null => String::new(),
28 JsonValue::Bool(b) => b.to_string(),
29 JsonValue::Number(n) => {
30 if *n == n.floor() && n.abs() < 1e15 {
31 format!("{}", *n as i64)
32 } else {
33 format!("{}", n)
34 }
35 }
36 JsonValue::String(s) => s.clone(),
37 JsonValue::Array(_) => "[Array]".to_string(),
38 JsonValue::Object(_) => "[Object]".to_string(),
39 }
40 }
41}
42
43#[derive(Debug)]
44pub struct JsonError {
45 pub message: String,
46}
47
48impl std::fmt::Display for JsonError {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 write!(f, "JsonError: {}", self.message)
51 }
52}
53
54impl std::error::Error for JsonError {}
55
56pub fn parse(input: &str) -> Result<JsonValue, JsonError> {
57 let input = input.trim();
58 let (value, rest) = parse_value(input)?;
59 let rest = rest.trim();
60 if !rest.is_empty() {
61 return Err(JsonError { message: format!("Unexpected trailing content: {}", rest) });
62 }
63 Ok(value)
64}
65
66fn parse_value(input: &str) -> Result<(JsonValue, &str), JsonError> {
67 let input = input.trim_start();
68 if input.is_empty() {
69 return Err(JsonError { message: "Unexpected end of input".to_string() });
70 }
71 match input.chars().next().unwrap() {
72 '{' => parse_object(input),
73 '[' => parse_array(input),
74 '"' => {
75 let (s, rest) = parse_string(input)?;
76 Ok((JsonValue::String(s), rest))
77 }
78 't' => {
79 if input.starts_with("true") {
80 Ok((JsonValue::Bool(true), &input[4..]))
81 } else {
82 Err(JsonError { message: format!("Invalid token: {}", &input[..input.len().min(10)]) })
83 }
84 }
85 'f' => {
86 if input.starts_with("false") {
87 Ok((JsonValue::Bool(false), &input[5..]))
88 } else {
89 Err(JsonError { message: format!("Invalid token: {}", &input[..input.len().min(10)]) })
90 }
91 }
92 'n' => {
93 if input.starts_with("null") {
94 Ok((JsonValue::Null, &input[4..]))
95 } else {
96 Err(JsonError { message: format!("Invalid token: {}", &input[..input.len().min(10)]) })
97 }
98 }
99 c if c == '-' || c.is_ascii_digit() => parse_number(input),
100 c => Err(JsonError { message: format!("Unexpected character: '{}'", c) }),
101 }
102}
103
104fn parse_object(input: &str) -> Result<(JsonValue, &str), JsonError> {
105 let input = input.trim_start();
106 if !input.starts_with('{') {
107 return Err(JsonError { message: "Expected '{'".to_string() });
108 }
109 let mut rest = input[1..].trim_start();
110 let mut map = HashMap::new();
111 if rest.starts_with('}') {
112 return Ok((JsonValue::Object(map), &rest[1..]));
113 }
114 loop {
115 rest = rest.trim_start();
116 let (key, after_key) = parse_string(rest)?;
117 rest = after_key.trim_start();
118 if !rest.starts_with(':') {
119 return Err(JsonError { message: "Expected ':'".to_string() });
120 }
121 rest = rest[1..].trim_start();
122 let (value, after_value) = parse_value(rest)?;
123 map.insert(key, value);
124 rest = after_value.trim_start();
125 match rest.chars().next() {
126 Some(',') => { rest = rest[1..].trim_start(); }
127 Some('}') => { return Ok((JsonValue::Object(map), &rest[1..])); }
128 _ => return Err(JsonError { message: "Expected ',' or '}'".to_string() }),
129 }
130 }
131}
132
133fn parse_array(input: &str) -> Result<(JsonValue, &str), JsonError> {
134 let input = input.trim_start();
135 if !input.starts_with('[') {
136 return Err(JsonError { message: "Expected '['".to_string() });
137 }
138 let mut rest = input[1..].trim_start();
139 let mut arr = Vec::new();
140 if rest.starts_with(']') {
141 return Ok((JsonValue::Array(arr), &rest[1..]));
142 }
143 loop {
144 rest = rest.trim_start();
145 let (value, after_value) = parse_value(rest)?;
146 arr.push(value);
147 rest = after_value.trim_start();
148 match rest.chars().next() {
149 Some(',') => { rest = rest[1..].trim_start(); }
150 Some(']') => { return Ok((JsonValue::Array(arr), &rest[1..])); }
151 _ => return Err(JsonError { message: "Expected ',' or ']'".to_string() }),
152 }
153 }
154}
155
156fn parse_string(input: &str) -> Result<(String, &str), JsonError> {
157 if !input.starts_with('"') {
158 return Err(JsonError { message: "Expected '\"'".to_string() });
159 }
160 let bytes = input.as_bytes();
161 let mut i = 1;
162 let mut s = String::new();
163 while i < bytes.len() {
164 match bytes[i] {
165 b'"' => {
166 return Ok((s, &input[i + 1..]));
167 }
168 b'\\' => {
169 i += 1;
170 if i >= bytes.len() {
171 return Err(JsonError { message: "Unexpected end of string escape".to_string() });
172 }
173 match bytes[i] {
174 b'"' => s.push('"'),
175 b'\\' => s.push('\\'),
176 b'/' => s.push('/'),
177 b'n' => s.push('\n'),
178 b't' => s.push('\t'),
179 b'r' => s.push('\r'),
180 b'b' => s.push('\x08'),
181 b'f' => s.push('\x0C'),
182 b'u' => {
183 if i + 4 >= bytes.len() {
184 return Err(JsonError { message: "Invalid unicode escape".to_string() });
185 }
186 let hex = &input[i + 1..i + 5];
187 let code_point = u32::from_str_radix(hex, 16).map_err(|_| JsonError {
188 message: format!("Invalid unicode escape: \\u{}", hex),
189 })?;
190 let ch = char::from_u32(code_point).ok_or_else(|| JsonError {
191 message: format!("Invalid unicode code point: {}", code_point),
192 })?;
193 s.push(ch);
194 i += 4;
195 }
196 c => {
197 return Err(JsonError { message: format!("Unknown escape sequence: \\{}", c as char) });
198 }
199 }
200 i += 1;
201 }
202 b => {
203 s.push(b as char);
204 i += 1;
205 }
206 }
207 }
208 Err(JsonError { message: "Unterminated string".to_string() })
209}
210
211fn parse_number(input: &str) -> Result<(JsonValue, &str), JsonError> {
212 let bytes = input.as_bytes();
213 let mut i = 0;
214 if i < bytes.len() && bytes[i] == b'-' {
215 i += 1;
216 }
217 while i < bytes.len() && bytes[i].is_ascii_digit() {
218 i += 1;
219 }
220 if i < bytes.len() && bytes[i] == b'.' {
221 i += 1;
222 while i < bytes.len() && bytes[i].is_ascii_digit() {
223 i += 1;
224 }
225 }
226 if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
227 i += 1;
228 if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
229 i += 1;
230 }
231 while i < bytes.len() && bytes[i].is_ascii_digit() {
232 i += 1;
233 }
234 }
235 let num_str = &input[..i];
236 let n: f64 = num_str.parse().map_err(|_| JsonError {
237 message: format!("Invalid number: {}", num_str),
238 })?;
239 Ok((JsonValue::Number(n), &input[i..]))
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_parse_object() {
248 let v = parse(r#"{"name": "Alice", "age": 30}"#).unwrap();
249 if let JsonValue::Object(map) = v {
250 assert_eq!(map.get("name").unwrap().to_display_string(), "Alice");
251 assert_eq!(map.get("age").unwrap().to_display_string(), "30");
252 } else {
253 panic!("Expected object");
254 }
255 }
256
257 #[test]
258 fn test_parse_array() {
259 let v = parse(r#"[1, 2, 3]"#).unwrap();
260 if let JsonValue::Array(arr) = v {
261 assert_eq!(arr.len(), 3);
262 assert_eq!(arr[0].to_display_string(), "1");
263 } else {
264 panic!("Expected array");
265 }
266 }
267
268 #[test]
269 fn test_parse_string_with_escapes() {
270 let v = parse(r#""hello\nworld\t!""#).unwrap();
271 if let JsonValue::String(s) = v {
272 assert_eq!(s, "hello\nworld\t!");
273 } else {
274 panic!("Expected string");
275 }
276 }
277
278 #[test]
279 fn test_parse_number() {
280 let v = parse("3.14").unwrap();
281 if let JsonValue::Number(n) = v {
282 assert!((n - 3.14).abs() < 1e-10);
283 } else {
284 panic!("Expected number");
285 }
286 }
287
288 #[test]
289 fn test_parse_bool() {
290 assert!(matches!(parse("true").unwrap(), JsonValue::Bool(true)));
291 assert!(matches!(parse("false").unwrap(), JsonValue::Bool(false)));
292 }
293
294 #[test]
295 fn test_parse_null() {
296 assert!(matches!(parse("null").unwrap(), JsonValue::Null));
297 }
298}