1use crate::error::{Error, Result};
7use std::collections::BTreeMap;
8use std::fmt::Write as _;
9
10#[derive(Debug, Clone, PartialEq)]
15pub enum Json {
16 Null,
17 Bool(bool),
18 Number(f64),
19 String(String),
20 Array(Vec<Json>),
21 Object(BTreeMap<String, Json>),
22}
23
24impl Json {
25 pub fn object<K: Into<String>, I: IntoIterator<Item = (K, Json)>>(pairs: I) -> Self {
27 Json::Object(pairs.into_iter().map(|(k, v)| (k.into(), v)).collect())
28 }
29
30 pub fn get(&self, path: &str) -> Option<&Json> {
34 let mut current = self;
35 for segment in path.split('.') {
36 current = match current {
37 Json::Object(map) => map.get(segment)?,
38 Json::Array(items) => items.get(segment.parse::<usize>().ok()?)?,
39 _ => return None,
40 };
41 }
42 Some(current)
43 }
44
45 pub fn as_str(&self) -> Option<&str> {
46 match self {
47 Json::String(s) => Some(s),
48 _ => None,
49 }
50 }
51
52 pub fn as_f64(&self) -> Option<f64> {
53 match self {
54 Json::Number(n) => Some(*n),
55 _ => None,
56 }
57 }
58
59 pub fn as_i64(&self) -> Option<i64> {
60 self.as_f64().map(|n| n as i64)
61 }
62
63 pub fn as_bool(&self) -> Option<bool> {
64 match self {
65 Json::Bool(b) => Some(*b),
66 _ => None,
67 }
68 }
69
70 pub fn as_array(&self) -> Option<&[Json]> {
71 match self {
72 Json::Array(items) => Some(items),
73 _ => None,
74 }
75 }
76
77 pub fn as_object(&self) -> Option<&BTreeMap<String, Json>> {
78 match self {
79 Json::Object(map) => Some(map),
80 _ => None,
81 }
82 }
83
84 pub fn is_null(&self) -> bool {
85 matches!(self, Json::Null)
86 }
87
88 pub fn to_string_pretty(&self) -> String {
90 let mut out = String::new();
91 self.write(&mut out, Some(2), 0);
92 out
93 }
94
95 fn estimated_len(&self) -> usize {
101 match self {
102 Json::Null => 4,
103 Json::Bool(true) => 4,
104 Json::Bool(false) => 5,
105 Json::Number(_) => 20,
107 Json::String(s) => s.len() + 8,
109 Json::Array(items) => {
110 2 + items.iter().map(Json::estimated_len).sum::<usize>() + items.len()
111 }
112 Json::Object(map) => {
113 2 + map
114 .iter()
115 .map(|(key, value)| key.len() + 4 + value.estimated_len())
116 .sum::<usize>()
117 + map.len()
118 }
119 }
120 }
121
122 fn write(&self, out: &mut String, indent: Option<usize>, depth: usize) {
123 let newline = if indent.is_some() { "\n" } else { "" };
127 let colon = if indent.is_some() { ": " } else { ":" };
128 let (pad, pad_close) = match indent {
129 Some(width) => (" ".repeat(width * (depth + 1)), " ".repeat(width * depth)),
130 None => (String::new(), String::new()),
131 };
132
133 match self {
134 Json::Null => out.push_str("null"),
135 Json::Bool(true) => out.push_str("true"),
136 Json::Bool(false) => out.push_str("false"),
137 Json::Number(n) => {
138 if n.is_finite() {
139 if n.fract() == 0.0 && n.abs() < 1e15 {
141 push_integer(out, *n as i64);
142 } else {
143 let _ = write!(out, "{n}");
144 }
145 } else {
146 out.push_str("null");
147 }
148 }
149 Json::String(s) => escape_into(s, out),
150 Json::Array(items) => {
151 if items.is_empty() {
152 out.push_str("[]");
153 return;
154 }
155 out.push('[');
156 for (i, item) in items.iter().enumerate() {
157 if i > 0 {
158 out.push(',');
159 }
160 out.push_str(newline);
161 out.push_str(&pad);
162 item.write(out, indent, depth + 1);
163 }
164 out.push_str(newline);
165 out.push_str(&pad_close);
166 out.push(']');
167 }
168 Json::Object(map) => {
169 if map.is_empty() {
170 out.push_str("{}");
171 return;
172 }
173 out.push('{');
174 for (i, (key, value)) in map.iter().enumerate() {
175 if i > 0 {
176 out.push(',');
177 }
178 out.push_str(newline);
179 out.push_str(&pad);
180 escape_into(key, out);
181 out.push_str(colon);
182 value.write(out, indent, depth + 1);
183 }
184 out.push_str(newline);
185 out.push_str(&pad_close);
186 out.push('}');
187 }
188 }
189 }
190
191 pub fn parse(input: &str) -> Result<Json> {
193 let mut parser = Parser { bytes: input.as_bytes(), pos: 0 };
194 parser.skip_whitespace();
195 let value = parser.value()?;
196 parser.skip_whitespace();
197 if parser.pos < parser.bytes.len() {
198 return Err(parser.error("unexpected trailing characters"));
199 }
200 Ok(value)
201 }
202}
203
204impl std::fmt::Display for Json {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 let mut out = String::with_capacity(self.estimated_len());
208 self.write(&mut out, None, 0);
209 f.write_str(&out)
210 }
211}
212
213const CONTROL_ESCAPES: [&str; 32] = [
216 "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007",
217 "\\u0008", "\\u0009", "\\u000a", "\\u000b", "\\u000c", "\\u000d", "\\u000e", "\\u000f",
218 "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017",
219 "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f",
220];
221
222fn escape_into(s: &str, out: &mut String) {
229 out.push('"');
230
231 let bytes = s.as_bytes();
232 let mut clean_from = 0;
233
234 for (index, &byte) in bytes.iter().enumerate() {
235 let replacement: &str = match byte {
236 b'"' => "\\\"",
237 b'\\' => "\\\\",
238 b'\n' => "\\n",
239 b'\r' => "\\r",
240 b'\t' => "\\t",
241 0x08 => "\\b",
242 0x0c => "\\f",
243 b'<' => "\\u003c",
245 0x00..=0x1f => CONTROL_ESCAPES[byte as usize],
246 _ => continue,
247 };
248
249 out.push_str(&s[clean_from..index]);
250 out.push_str(replacement);
251 clean_from = index + 1;
252 }
253
254 out.push_str(&s[clean_from..]);
255 out.push('"');
256}
257
258fn push_integer(out: &mut String, value: i64) {
263 if value == 0 {
264 out.push('0');
265 return;
266 }
267
268 let mut digits = [0u8; 20];
269 let mut index = digits.len();
270 let mut magnitude = value.unsigned_abs();
272
273 while magnitude > 0 {
274 index -= 1;
275 digits[index] = b'0' + (magnitude % 10) as u8;
276 magnitude /= 10;
277 }
278 if value < 0 {
279 index -= 1;
280 digits[index] = b'-';
281 }
282
283 out.push_str(std::str::from_utf8(&digits[index..]).unwrap_or("0"));
284}
285
286impl From<bool> for Json {
287 fn from(v: bool) -> Self {
288 Json::Bool(v)
289 }
290}
291
292impl From<String> for Json {
293 fn from(v: String) -> Self {
294 Json::String(v)
295 }
296}
297
298impl From<&str> for Json {
299 fn from(v: &str) -> Self {
300 Json::String(v.to_string())
301 }
302}
303
304impl<T: Into<Json>> From<Option<T>> for Json {
305 fn from(v: Option<T>) -> Self {
306 v.map_or(Json::Null, Into::into)
307 }
308}
309
310impl<T: Into<Json>> From<Vec<T>> for Json {
311 fn from(v: Vec<T>) -> Self {
312 Json::Array(v.into_iter().map(Into::into).collect())
313 }
314}
315
316macro_rules! impl_from_number {
317 ($($t:ty),*) => {
318 $(impl From<$t> for Json {
319 fn from(v: $t) -> Self {
320 Json::Number(v as f64)
321 }
322 })*
323 };
324}
325impl_from_number!(i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64);
326
327struct Parser<'a> {
328 bytes: &'a [u8],
329 pos: usize,
330}
331
332impl<'a> Parser<'a> {
333 fn error(&self, message: &str) -> Error {
334 let consumed = &self.bytes[..self.pos.min(self.bytes.len())];
336 let line = consumed.iter().filter(|b| **b == b'\n').count() + 1;
337 let column = consumed.iter().rposition(|b| *b == b'\n').map_or(self.pos, |i| self.pos - i - 1) + 1;
338 Error::Json { line, column, message: message.to_string() }
339 }
340
341 fn peek(&self) -> Option<u8> {
342 self.bytes.get(self.pos).copied()
343 }
344
345 fn skip_whitespace(&mut self) {
346 while matches!(self.peek(), Some(b' ' | b'\t' | b'\n' | b'\r')) {
347 self.pos += 1;
348 }
349 }
350
351 fn expect(&mut self, byte: u8) -> Result<()> {
352 if self.peek() == Some(byte) {
353 self.pos += 1;
354 Ok(())
355 } else {
356 Err(self.error(&format!("expected `{}`", byte as char)))
357 }
358 }
359
360 fn literal(&mut self, word: &str, value: Json) -> Result<Json> {
361 if self.bytes[self.pos..].starts_with(word.as_bytes()) {
362 self.pos += word.len();
363 Ok(value)
364 } else {
365 Err(self.error("invalid literal"))
366 }
367 }
368
369 fn value(&mut self) -> Result<Json> {
370 match self.peek() {
371 Some(b'n') => self.literal("null", Json::Null),
372 Some(b't') => self.literal("true", Json::Bool(true)),
373 Some(b'f') => self.literal("false", Json::Bool(false)),
374 Some(b'"') => self.string().map(Json::String),
375 Some(b'[') => self.array(),
376 Some(b'{') => self.object(),
377 Some(b'-' | b'0'..=b'9') => self.number(),
378 Some(_) => Err(self.error("unexpected character")),
379 None => Err(self.error("unexpected end of input")),
380 }
381 }
382
383 fn array(&mut self) -> Result<Json> {
384 self.expect(b'[')?;
385 let mut items = Vec::new();
386 self.skip_whitespace();
387 if self.peek() == Some(b']') {
388 self.pos += 1;
389 return Ok(Json::Array(items));
390 }
391 loop {
392 self.skip_whitespace();
393 items.push(self.value()?);
394 self.skip_whitespace();
395 match self.peek() {
396 Some(b',') => self.pos += 1,
397 Some(b']') => {
398 self.pos += 1;
399 return Ok(Json::Array(items));
400 }
401 _ => return Err(self.error("expected `,` or `]`")),
402 }
403 }
404 }
405
406 fn object(&mut self) -> Result<Json> {
407 self.expect(b'{')?;
408 let mut map = BTreeMap::new();
409 self.skip_whitespace();
410 if self.peek() == Some(b'}') {
411 self.pos += 1;
412 return Ok(Json::Object(map));
413 }
414 loop {
415 self.skip_whitespace();
416 let key = self.string()?;
417 self.skip_whitespace();
418 self.expect(b':')?;
419 self.skip_whitespace();
420 map.insert(key, self.value()?);
421 self.skip_whitespace();
422 match self.peek() {
423 Some(b',') => self.pos += 1,
424 Some(b'}') => {
425 self.pos += 1;
426 return Ok(Json::Object(map));
427 }
428 _ => return Err(self.error("expected `,` or `}`")),
429 }
430 }
431 }
432
433 fn number(&mut self) -> Result<Json> {
434 let start = self.pos;
435 if self.peek() == Some(b'-') {
436 self.pos += 1;
437 }
438 while matches!(self.peek(), Some(b'0'..=b'9' | b'.' | b'e' | b'E' | b'+' | b'-')) {
439 self.pos += 1;
440 }
441 std::str::from_utf8(&self.bytes[start..self.pos])
442 .ok()
443 .and_then(|s| s.parse::<f64>().ok())
444 .map(Json::Number)
445 .ok_or_else(|| self.error("invalid number"))
446 }
447
448 fn string(&mut self) -> Result<String> {
449 self.expect(b'"')?;
450 let mut out = String::new();
451 loop {
452 match self.peek() {
453 None => return Err(self.error("unterminated string")),
454 Some(b'"') => {
455 self.pos += 1;
456 return Ok(out);
457 }
458 Some(b'\\') => {
459 self.pos += 1;
460 let escape = self.peek().ok_or_else(|| self.error("unterminated escape"))?;
461 self.pos += 1;
462 match escape {
463 b'"' => out.push('"'),
464 b'\\' => out.push('\\'),
465 b'/' => out.push('/'),
466 b'n' => out.push('\n'),
467 b'r' => out.push('\r'),
468 b't' => out.push('\t'),
469 b'b' => out.push('\u{08}'),
470 b'f' => out.push('\u{0c}'),
471 b'u' => out.push(self.unicode_escape()?),
472 _ => return Err(self.error("invalid escape sequence")),
473 }
474 }
475 Some(_) => {
476 let start = self.pos;
478 while let Some(b) = self.peek() {
479 if b == b'"' || b == b'\\' {
480 break;
481 }
482 self.pos += 1;
483 }
484 match std::str::from_utf8(&self.bytes[start..self.pos]) {
485 Ok(chunk) => out.push_str(chunk),
486 Err(_) => return Err(self.error("invalid UTF-8 in string")),
487 }
488 }
489 }
490 }
491 }
492
493 fn unicode_escape(&mut self) -> Result<char> {
494 let high = self.hex4()?;
495 if (0xD800..0xDC00).contains(&high) {
497 if self.peek() == Some(b'\\') && self.bytes.get(self.pos + 1) == Some(&b'u') {
498 self.pos += 2;
499 let low = self.hex4()?;
500 if (0xDC00..0xE000).contains(&low) {
501 let combined = 0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00);
502 return char::from_u32(combined).ok_or_else(|| self.error("invalid code point"));
503 }
504 }
505 return Err(self.error("unpaired surrogate"));
506 }
507 char::from_u32(high).ok_or_else(|| self.error("invalid code point"))
508 }
509
510 fn hex4(&mut self) -> Result<u32> {
511 if self.pos + 4 > self.bytes.len() {
512 return Err(self.error("truncated \\u escape"));
513 }
514 let hex = std::str::from_utf8(&self.bytes[self.pos..self.pos + 4])
515 .ok()
516 .and_then(|s| u32::from_str_radix(s, 16).ok())
517 .ok_or_else(|| self.error("invalid \\u escape"))?;
518 self.pos += 4;
519 Ok(hex)
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526
527 #[test]
528 fn parses_and_reserializes_a_document() {
529 let source = r#"{"name":"Rustlavel","stars":42,"tags":["web","rust"],"nested":{"ok":true,"nothing":null}}"#;
530 let value = Json::parse(source).unwrap();
531
532 assert_eq!(value.get("name").unwrap().as_str(), Some("Rustlavel"));
533 assert_eq!(value.get("stars").unwrap().as_i64(), Some(42));
534 assert_eq!(value.get("tags.1").unwrap().as_str(), Some("rust"));
535 assert_eq!(value.get("nested.ok").unwrap().as_bool(), Some(true));
536 assert!(value.get("nested.nothing").unwrap().is_null());
537 assert!(value.get("nested.missing").is_none());
538
539 assert_eq!(
542 value.to_string(),
543 r#"{"name":"Rustlavel","nested":{"nothing":null,"ok":true},"stars":42,"tags":["web","rust"]}"#
544 );
545 assert_eq!(Json::parse(&value.to_string()).unwrap(), value);
546 }
547
548 #[test]
549 fn handles_escapes_and_surrogate_pairs() {
550 let value = Json::parse(r#""line\nbreak é 🚀""#).unwrap();
551 assert_eq!(value.as_str(), Some("line\nbreak é 🚀"));
552
553 let embedded = Json::from("</script>").to_string();
555 assert_eq!(embedded, "\"\\u003c/script>\"");
556 assert_eq!(Json::parse(&embedded).unwrap().as_str(), Some("</script>"));
557 }
558
559 #[test]
560 fn integral_numbers_do_not_grow_a_decimal_point() {
561 assert_eq!(Json::from(42).to_string(), "42");
562 assert_eq!(Json::from(1.5).to_string(), "1.5");
563 }
564
565 #[test]
566 fn reports_position_of_a_syntax_error() {
567 let err = Json::parse("{\n \"a\": tru\n}").unwrap_err();
568 match err {
569 Error::Json { line, .. } => assert_eq!(line, 2),
570 other => panic!("expected a JSON error, got {other:?}"),
571 }
572 }
573
574 #[test]
575 fn pretty_printing_round_trips() {
576 let value = Json::parse(r#"{"a":[1,2],"b":{}}"#).unwrap();
577 let pretty = value.to_string_pretty();
578 assert!(pretty.contains("\n \"a\": ["));
579 assert_eq!(Json::parse(&pretty).unwrap(), value);
580 }
581}