1use std::fmt;
5
6#[derive(Clone, Debug, PartialEq)]
8pub enum JsonValue {
9 Null,
10 Bool(bool),
11 Integer(i64),
12 Number(f64),
13 Decimal(String),
14 String(String),
15 Array(Vec<JsonValue>),
16 Object(Vec<(String, JsonValue)>),
17}
18
19impl JsonValue {
20 pub fn as_str(&self) -> Option<&str> {
22 match self {
23 JsonValue::String(s) => Some(s.as_str()),
24 _ => None,
25 }
26 }
27
28 pub fn as_f64(&self) -> Option<f64> {
30 match self {
31 JsonValue::Number(n) => Some(*n),
32 JsonValue::Integer(n) => Some(*n as f64),
33 JsonValue::Decimal(n) => n.parse::<f64>().ok(),
34 _ => None,
35 }
36 }
37
38 pub fn as_i64(&self) -> Option<i64> {
40 match self {
41 JsonValue::Integer(n) => Some(*n),
42 _ => None,
43 }
44 }
45
46 pub fn as_bool(&self) -> Option<bool> {
48 match self {
49 JsonValue::Bool(b) => Some(*b),
50 _ => None,
51 }
52 }
53
54 pub fn as_array(&self) -> Option<&[JsonValue]> {
56 match self {
57 JsonValue::Array(items) => Some(items.as_slice()),
58 _ => None,
59 }
60 }
61
62 pub fn as_array_mut(&mut self) -> Option<&mut Vec<JsonValue>> {
64 match self {
65 JsonValue::Array(items) => Some(items),
66 _ => None,
67 }
68 }
69
70 pub fn as_object(&self) -> Option<&[(String, JsonValue)]> {
72 match self {
73 JsonValue::Object(entries) => Some(entries.as_slice()),
74 _ => None,
75 }
76 }
77
78 pub fn as_object_mut(&mut self) -> Option<&mut Vec<(String, JsonValue)>> {
80 match self {
81 JsonValue::Object(entries) => Some(entries),
82 _ => None,
83 }
84 }
85
86 pub fn get(&self, key: &str) -> Option<&JsonValue> {
88 if let JsonValue::Object(entries) = self {
89 for (k, v) in entries {
90 if k == key {
91 return Some(v);
92 }
93 }
94 }
95 None
96 }
97
98 pub fn get_mut(&mut self, key: &str) -> Option<&mut JsonValue> {
100 if let JsonValue::Object(entries) = self {
101 for (k, v) in entries.iter_mut() {
102 if k == key {
103 return Some(v);
104 }
105 }
106 }
107 None
108 }
109
110 pub fn object(entries: Vec<(String, JsonValue)>) -> JsonValue {
112 JsonValue::Object(entries)
113 }
114
115 pub fn array(items: Vec<JsonValue>) -> JsonValue {
117 JsonValue::Array(items)
118 }
119
120 #[deprecated(
132 note = "Use crate::serde_json::Value::to_string_compact for boundary emission; see ADR 0010 / issue #177"
133 )]
134 pub fn to_json_string(&self) -> String {
135 let mut out = String::new();
136 self.write_json(&mut out);
137 out
138 }
139
140 fn write_json(&self, out: &mut String) {
141 match self {
142 JsonValue::Null => out.push_str("null"),
143 JsonValue::Bool(b) => out.push_str(if *b { "true" } else { "false" }),
144 JsonValue::Integer(n) => out.push_str(&format!("{n}")),
145 JsonValue::Number(n) => {
146 if n.fract() == 0.0 {
147 out.push_str(&format!("{}", *n as i64));
148 } else {
149 out.push_str(&format!("{}", n));
150 }
151 }
152 JsonValue::Decimal(n) => out.push_str(n),
153 JsonValue::String(s) => {
154 out.push('"');
155 for ch in s.chars() {
156 match ch {
157 '"' => out.push_str("\\\""),
158 '\\' => out.push_str("\\\\"),
159 '\n' => out.push_str("\\n"),
160 '\r' => out.push_str("\\r"),
161 '\t' => out.push_str("\\t"),
162 c if c.is_control() => {
163 out.push_str(&format!("\\u{:04x}", c as u32));
164 }
165 c => out.push(c),
166 }
167 }
168 out.push('"');
169 }
170 JsonValue::Array(items) => {
171 out.push('[');
172 for (idx, item) in items.iter().enumerate() {
173 if idx > 0 {
174 out.push(',');
175 }
176 item.write_json(out);
177 }
178 out.push(']');
179 }
180 JsonValue::Object(entries) => {
181 out.push('{');
182 for (idx, (key, value)) in entries.iter().enumerate() {
183 if idx > 0 {
184 out.push(',');
185 }
186 JsonValue::String(key.clone()).write_json(out);
187 out.push(':');
188 value.write_json(out);
189 }
190 out.push('}');
191 }
192 }
193 }
194}
195
196impl From<&str> for JsonValue {
197 fn from(value: &str) -> JsonValue {
198 JsonValue::String(value.to_string())
199 }
200}
201
202impl From<String> for JsonValue {
203 fn from(value: String) -> JsonValue {
204 JsonValue::String(value)
205 }
206}
207
208impl From<bool> for JsonValue {
209 fn from(value: bool) -> JsonValue {
210 JsonValue::Bool(value)
211 }
212}
213
214impl From<f64> for JsonValue {
215 fn from(value: f64) -> JsonValue {
216 JsonValue::Number(value)
217 }
218}
219
220impl From<i64> for JsonValue {
221 fn from(value: i64) -> JsonValue {
222 JsonValue::Integer(value)
223 }
224}
225
226impl From<usize> for JsonValue {
227 fn from(value: usize) -> JsonValue {
228 i64::try_from(value)
229 .map(JsonValue::Integer)
230 .unwrap_or_else(|_| JsonValue::Number(value as f64))
231 }
232}
233
234impl fmt::Display for JsonValue {
235 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 #[allow(deprecated)]
241 let s = self.to_json_string();
242 write!(f, "{s}")
243 }
244}
245
246pub struct JsonParser<'a> {
248 input: &'a [u8],
249 pos: usize,
250}
251
252impl<'a> JsonParser<'a> {
253 pub fn new(input: &'a str) -> Self {
255 Self {
256 input: input.as_bytes(),
257 pos: 0,
258 }
259 }
260
261 pub fn parse_value(&mut self) -> Result<JsonValue, String> {
263 self.skip_whitespace();
264 if self.eof() {
265 return Err("unexpected end of input".to_string());
266 }
267 let ch = self.current_char();
268 match ch {
269 b'n' => self.parse_null(),
270 b't' | b'f' => self.parse_bool(),
271 b'-' | b'0'..=b'9' => self.parse_number(),
272 b'"' => self.parse_string().map(JsonValue::String),
273 b'[' => self.parse_array(),
274 b'{' => self.parse_object(),
275 _ => Err(format!("unexpected character '{}'", ch as char)),
276 }
277 }
278
279 fn parse_null(&mut self) -> Result<JsonValue, String> {
280 self.expect_bytes(b"null")?;
281 Ok(JsonValue::Null)
282 }
283
284 fn parse_bool(&mut self) -> Result<JsonValue, String> {
285 if self.matches_bytes(b"true") {
286 self.pos += 4;
287 Ok(JsonValue::Bool(true))
288 } else if self.matches_bytes(b"false") {
289 self.pos += 5;
290 Ok(JsonValue::Bool(false))
291 } else {
292 Err("invalid boolean literal".to_string())
293 }
294 }
295
296 fn parse_number(&mut self) -> Result<JsonValue, String> {
297 let start = self.pos;
298 if self.current_char() == b'-' {
299 self.pos += 1;
300 }
301 if self.eof() {
302 return Err("invalid number literal".to_string());
303 }
304
305 match self.current_char() {
306 b'0' => {
307 self.pos += 1;
308 }
309 b'1'..=b'9' => {
310 self.pos += 1;
311 while !self.eof() && self.current_char().is_ascii_digit() {
312 self.pos += 1;
313 }
314 }
315 _ => return Err("invalid number literal".to_string()),
316 }
317
318 let mut is_float = false;
319
320 if !self.eof() && self.current_char() == b'.' {
321 is_float = true;
322 self.pos += 1;
323 if self.eof() || !self.current_char().is_ascii_digit() {
324 return Err("invalid number literal".to_string());
325 }
326 while !self.eof() && self.current_char().is_ascii_digit() {
327 self.pos += 1;
328 }
329 }
330
331 if !self.eof() && (self.current_char() == b'e' || self.current_char() == b'E') {
332 is_float = true;
333 self.pos += 1;
334 if !self.eof() && (self.current_char() == b'+' || self.current_char() == b'-') {
335 self.pos += 1;
336 }
337 if self.eof() || !self.current_char().is_ascii_digit() {
338 return Err("invalid number literal".to_string());
339 }
340 while !self.eof() && self.current_char().is_ascii_digit() {
341 self.pos += 1;
342 }
343 }
344
345 let slice = &self.input[start..self.pos];
346 let s = std::str::from_utf8(slice).map_err(|_| "invalid UTF-8 in number".to_string())?;
347 if !is_float {
348 if let Ok(value) = s.parse::<i64>() {
349 return Ok(JsonValue::Integer(value));
350 }
351 return Ok(JsonValue::Decimal(s.to_string()));
352 }
353 if should_preserve_decimal_text(s) {
354 return Ok(JsonValue::Decimal(s.to_string()));
355 }
356 let value = s
357 .parse::<f64>()
358 .map_err(|_| "failed to parse number".to_string())?;
359 if !value.is_finite() {
360 return Ok(JsonValue::Decimal(s.to_string()));
361 }
362 Ok(JsonValue::Number(value))
363 }
364
365 fn parse_string(&mut self) -> Result<String, String> {
366 self.expect_char(b'"')?;
367 let mut result = String::new();
368 while !self.eof() {
369 let ch = self.current_char();
370 match ch {
371 b'"' => {
372 self.pos += 1;
373 return Ok(result);
374 }
375 b'\\' => {
376 self.pos += 1;
377 if self.eof() {
378 return Err("unexpected end of input in escape".to_string());
379 }
380 let esc = self.current_char();
381 self.pos += 1;
382 match esc {
383 b'"' => result.push('"'),
384 b'\\' => result.push('\\'),
385 b'/' => result.push('/'),
386 b'b' => result.push('\x08'),
387 b'f' => result.push('\x0c'),
388 b'n' => result.push('\n'),
389 b'r' => result.push('\r'),
390 b't' => result.push('\t'),
391 b'u' => {
392 let code = self.parse_unicode_escape()?;
398 if (0xD800..=0xDBFF).contains(&code) {
399 if self.pos + 2 > self.input.len()
402 || self.input[self.pos] != b'\\'
403 || self.input[self.pos + 1] != b'u'
404 {
405 return Err(
406 "expected low surrogate after high surrogate".to_string()
407 );
408 }
409 self.pos += 2;
410 let low = self.parse_unicode_escape()?;
411 if !(0xDC00..=0xDFFF).contains(&low) {
412 return Err("invalid low surrogate".to_string());
413 }
414 let scalar = 0x10000 + (((code - 0xD800) << 10) | (low - 0xDC00));
415 if let Some(chr) = char::from_u32(scalar) {
416 result.push(chr);
417 } else {
418 return Err("invalid unicode escape".to_string());
419 }
420 } else if (0xDC00..=0xDFFF).contains(&code) {
421 return Err(
422 "unexpected low surrogate without preceding high surrogate"
423 .to_string(),
424 );
425 } else if let Some(chr) = char::from_u32(code) {
426 result.push(chr);
427 } else {
428 return Err("invalid unicode escape".to_string());
429 }
430 }
431 _ => return Err("invalid escape sequence".to_string()),
432 }
433 }
434 _ if ch < 0x80 => {
435 self.pos += 1;
437 result.push(ch as char);
438 }
439 _ => {
440 let next = std::str::from_utf8(&self.input[self.pos..])
449 .map_err(|_| "invalid utf-8 in string body".to_string())?
450 .chars()
451 .next()
452 .ok_or_else(|| "unterminated string literal".to_string())?;
453 self.pos += next.len_utf8();
454 result.push(next);
455 }
456 }
457 }
458 Err("unterminated string literal".to_string())
459 }
460
461 fn parse_unicode_escape(&mut self) -> Result<u32, String> {
462 if self.pos + 4 > self.input.len() {
463 return Err("invalid unicode escape".to_string());
464 }
465 let mut value = 0u32;
466 for _ in 0..4 {
467 let ch = self.current_char();
468 self.pos += 1;
469 value <<= 4;
470 value |= match ch {
471 b'0'..=b'9' => (ch - b'0') as u32,
472 b'a'..=b'f' => (ch - b'a' + 10) as u32,
473 b'A'..=b'F' => (ch - b'A' + 10) as u32,
474 _ => return Err("invalid unicode escape".to_string()),
475 };
476 }
477 Ok(value)
478 }
479
480 fn parse_array(&mut self) -> Result<JsonValue, String> {
481 self.expect_char(b'[')?;
482 let mut items = Vec::new();
483 self.skip_whitespace();
484 if self.peek_char() == Some(b']') {
485 self.pos += 1;
486 return Ok(JsonValue::Array(items));
487 }
488 loop {
489 let value = self.parse_value()?;
490 items.push(value);
491 self.skip_whitespace();
492 match self.peek_char() {
493 Some(b',') => {
494 self.pos += 1;
495 }
496 Some(b']') => {
497 self.pos += 1;
498 break;
499 }
500 _ => return Err("expected ',' or ']' in array".to_string()),
501 }
502 }
503 Ok(JsonValue::Array(items))
504 }
505
506 fn parse_object(&mut self) -> Result<JsonValue, String> {
507 self.expect_char(b'{')?;
508 let mut entries = Vec::new();
509 self.skip_whitespace();
510 if self.peek_char() == Some(b'}') {
511 self.pos += 1;
512 return Ok(JsonValue::Object(entries));
513 }
514 loop {
515 self.skip_whitespace();
516 let key = self.parse_string()?;
517 self.skip_whitespace();
518 self.expect_char(b':')?;
519 let value = self.parse_value()?;
520 entries.push((key, value));
521 self.skip_whitespace();
522 match self.peek_char() {
523 Some(b',') => {
524 self.pos += 1;
525 }
526 Some(b'}') => {
527 self.pos += 1;
528 break;
529 }
530 _ => return Err("expected ',' or '}' in object".to_string()),
531 }
532 }
533 Ok(JsonValue::Object(entries))
534 }
535
536 fn skip_whitespace(&mut self) {
537 while !self.eof() {
538 match self.current_char() {
539 b' ' | b'\n' | b'\r' | b'\t' => self.pos += 1,
540 _ => break,
541 }
542 }
543 }
544
545 fn expect_bytes(&mut self, expected: &[u8]) -> Result<(), String> {
546 if self.remaining().starts_with(expected) {
547 self.pos += expected.len();
548 Ok(())
549 } else {
550 Err("unexpected token".to_string())
551 }
552 }
553
554 fn matches_bytes(&self, expected: &[u8]) -> bool {
555 self.remaining().starts_with(expected)
556 }
557
558 fn expect_char(&mut self, expected: u8) -> Result<(), String> {
559 if self.peek_char() == Some(expected) {
560 self.pos += 1;
561 Ok(())
562 } else {
563 Err(format!("expected '{}'", expected as char))
564 }
565 }
566
567 fn peek_char(&self) -> Option<u8> {
568 if self.pos >= self.input.len() {
569 None
570 } else {
571 Some(self.input[self.pos])
572 }
573 }
574
575 fn current_char(&self) -> u8 {
576 self.input[self.pos]
577 }
578
579 fn remaining(&self) -> &[u8] {
580 &self.input[self.pos..]
581 }
582
583 fn eof(&self) -> bool {
584 self.pos >= self.input.len()
585 }
586}
587
588fn should_preserve_decimal_text(input: &str) -> bool {
589 let significant_digits = input.bytes().filter(u8::is_ascii_digit).count();
590 significant_digits > 15
591}
592
593pub fn parse_json(input: &str) -> Result<JsonValue, String> {
595 let mut parser = JsonParser::new(input);
596 let value = parser.parse_value()?;
597 parser.skip_whitespace();
598 if parser.eof() {
599 Ok(value)
600 } else {
601 Err("unexpected trailing data".to_string())
602 }
603}
604
605#[cfg(test)]
606mod tests {
607 use super::*;
608 use proptest::prelude::*;
609
610 #[test]
611 fn parse_simple_object() {
612 let json = r#"{"name":"reddb","active":true,"count":3}"#;
613 let value = parse_json(json).unwrap();
614 let obj = value.as_object().unwrap();
615 assert_eq!(obj.len(), 3);
616 }
617
618 #[test]
619 fn parse_nested_array() {
620 let json = r#"{"items":[1,2,{"flag":false}]}"#;
621 let value = parse_json(json).unwrap();
622 assert!(value.get("items").unwrap().as_array().is_some());
623 }
624
625 #[test]
626 fn stringify_roundtrip() {
627 let json = r#"{"message":"hello","value":42}"#;
628 let value = parse_json(json).unwrap();
629 #[allow(deprecated)]
630 let output = value.to_json_string();
631 assert!(output.contains("hello"));
632 }
633
634 #[test]
635 fn constructors_accessors_mutators_and_display_cover_all_shapes() {
636 let mut value = JsonValue::object(vec![
637 ("name".to_string(), JsonValue::from("reddb")),
638 ("active".to_string(), JsonValue::from(true)),
639 ("count".to_string(), JsonValue::from(3usize)),
640 ("ratio".to_string(), JsonValue::from(1.5f64)),
641 (
642 "items".to_string(),
643 JsonValue::array(vec![JsonValue::Null, JsonValue::from(2i64)]),
644 ),
645 ]);
646
647 assert_eq!(value.get("name").and_then(JsonValue::as_str), Some("reddb"));
648 assert_eq!(value.get("active").and_then(JsonValue::as_bool), Some(true));
649 assert_eq!(value.get("count").and_then(JsonValue::as_f64), Some(3.0));
650 assert_eq!(
651 value
652 .get("items")
653 .and_then(JsonValue::as_array)
654 .map(<[_]>::len),
655 Some(2)
656 );
657 assert_eq!(value.as_object().map(<[_]>::len), Some(5));
658 assert!(JsonValue::Null.as_str().is_none());
659 assert!(JsonValue::Null.as_bool().is_none());
660 assert!(JsonValue::Null.as_array().is_none());
661 assert!(JsonValue::Null.as_object().is_none());
662
663 value
664 .get_mut("count")
665 .map(|slot| *slot = JsonValue::from(4usize))
666 .unwrap();
667 assert_eq!(value.get("count").and_then(JsonValue::as_f64), Some(4.0));
668
669 value
670 .as_object_mut()
671 .unwrap()
672 .push(("extra".to_string(), JsonValue::from("ok".to_string())));
673 let mut array = JsonValue::array(vec![JsonValue::from(1usize)]);
674 array.as_array_mut().unwrap().push(JsonValue::from(2usize));
675 assert_eq!(array.as_array().unwrap().len(), 2);
676
677 #[allow(deprecated)]
678 let encoded =
679 JsonValue::String("quote\" slash\\ tab\t ctrl\x01".to_string()).to_json_string();
680 assert!(encoded.contains("\\\""));
681 assert!(encoded.contains("\\\\"));
682 assert!(encoded.contains("\\t"));
683 assert!(encoded.contains("\\u0001"));
684 assert_eq!(format!("{}", JsonValue::Bool(false)), "false");
685 }
686
687 #[test]
688 fn parser_accepts_scalars_escapes_empty_containers_and_trailing_space() {
689 assert_eq!(parse_json(" null \n").unwrap(), JsonValue::Null);
690 assert_eq!(parse_json("true").unwrap(), JsonValue::Bool(true));
691 assert_eq!(parse_json("false").unwrap(), JsonValue::Bool(false));
692 assert_eq!(parse_json("-12.5e+2").unwrap(), JsonValue::Number(-1250.0));
693 assert_eq!(
694 parse_json("3.14159265358979323846").unwrap(),
695 JsonValue::Decimal("3.14159265358979323846".to_string())
696 );
697 assert_eq!(
698 parse_json("18446744073709551616").unwrap(),
699 JsonValue::Decimal("18446744073709551616".to_string())
700 );
701 assert_eq!(parse_json("[]").unwrap(), JsonValue::Array(Vec::new()));
702 assert_eq!(parse_json("{}").unwrap(), JsonValue::Object(Vec::new()));
703
704 let escaped = parse_json(r#""\"\\\/\b\f\n\r\t\u00e9\uD83D\uDCA9""#).unwrap();
705 let text = escaped.as_str().unwrap();
706 assert!(text.contains('"'));
707 assert!(text.contains('\\'));
708 assert!(text.contains('/'));
709 assert!(text.contains('\x08'));
710 assert!(text.contains('\x0c'));
711 assert!(text.contains('\n'));
712 assert!(text.contains('\r'));
713 assert!(text.contains('\t'));
714 assert!(text.chars().any(|ch| ch as u32 == 0x00E9));
715 assert_eq!(text.chars().last(), char::from_u32(0x1F4A9));
716 }
717
718 #[test]
719 fn parser_rejects_invalid_tokens_numbers_strings_and_separators() {
720 let cases = [
721 "",
722 "truth",
723 "nul",
724 "-",
725 "01",
726 "1.",
727 "1e",
728 r#""unterminated"#,
729 r#""\x""#,
730 r#""\u12""#,
731 r#""\u12zz""#,
732 r#""\uD800""#,
733 r#""\uD800\u0000""#,
734 r#""\uDC00""#,
735 "[1 2]",
736 "[1,]",
737 r#"{"a" 1}"#,
738 r#"{"a":1 "b":2}"#,
739 "{} trailing",
740 "?",
741 ];
742
743 for case in cases {
744 assert!(parse_json(case).is_err(), "{case:?}");
745 }
746 }
747
748 proptest! {
749 #[test]
750 fn beyond_native_decimal_text_round_trips(
751 prefix in "[1-9][0-9]{18,36}",
752 suffix in "[0-9]{1,24}"
753 ) {
754 let input = format!("{prefix}.{suffix}");
755 let parsed = parse_json(&input).expect("generated decimal parses");
756 prop_assert_eq!(&parsed, &JsonValue::Decimal(input.clone()));
757 #[allow(deprecated)]
758 let output = parsed.to_json_string();
759 prop_assert_eq!(output, input);
760 }
761 }
762}