Skip to main content

jsonc_parser/
parse_to_value.rs

1use super::ParseOptions;
2use super::errors::*;
3use super::tokens::Token;
4use super::value::*;
5use crate::map::Map;
6use crate::parser::JsoncParser;
7
8/// Parses a string containing JSONC to a `JsonValue`.
9///
10/// Returns `None` when the provided string is empty or whitespace.
11///
12/// # Example
13///
14/// ```
15/// use jsonc_parser::parse_to_value;
16///
17/// let json_value = parse_to_value(r#"{ "test": 5 } // test"#, &Default::default()).expect("Should parse.");
18/// ```
19pub fn parse_to_value<'a>(text: &'a str, options: &ParseOptions) -> Result<Option<JsonValue<'a>>, ParseError> {
20  let mut parser = JsoncParser::new(text, options);
21
22  let token = parser.scan()?;
23  let value = match token {
24    Some(token) => parse_value(&mut parser, token)?,
25    None => return Ok(None),
26  };
27
28  if parser.scan()?.is_some() {
29    return Err(
30      parser
31        .scanner
32        .create_error_for_current_token(ParseErrorKind::MultipleRootJsonValues),
33    );
34  }
35
36  Ok(Some(value))
37}
38
39fn parse_value<'a>(parser: &mut JsoncParser<'a>, token: Token<'a>) -> Result<JsonValue<'a>, ParseError> {
40  match token {
41    Token::OpenBrace => parse_object(parser),
42    Token::OpenBracket => parse_array(parser),
43    Token::String(s) => Ok(JsonValue::String(s)),
44    Token::Number(n) => Ok(JsonValue::Number(n)),
45    Token::Boolean(b) => Ok(JsonValue::Boolean(b)),
46    Token::Null => Ok(JsonValue::Null),
47    other => Err(parser.unexpected_token_error(&other)),
48  }
49}
50
51fn parse_object<'a>(parser: &mut JsoncParser<'a>) -> Result<JsonValue<'a>, ParseError> {
52  parser.enter_container()?;
53  let mut props = Map::default();
54  let mut first = true;
55
56  loop {
57    match parser.scan_object_entry(first)? {
58      None => break,
59      Some(key) => {
60        first = false;
61        let key_string = key.into_cow();
62        parser.scan_object_colon()?;
63        match parser.scan()? {
64          Some(value_token) => {
65            let value = parse_value(parser, value_token)?;
66            props.insert(key_string, value);
67          }
68          None => {
69            parser.exit_container();
70            return Err(
71              parser
72                .scanner
73                .create_error_for_current_token(ParseErrorKind::ExpectedObjectValue),
74            );
75          }
76        }
77      }
78    }
79  }
80
81  parser.exit_container();
82  Ok(JsonValue::Object(JsonObject::new(props)))
83}
84
85fn parse_array<'a>(parser: &mut JsoncParser<'a>) -> Result<JsonValue<'a>, ParseError> {
86  parser.enter_container()?;
87  let mut elements = Vec::new();
88
89  let mut token = parser.scan()?;
90
91  loop {
92    match token {
93      Some(Token::CloseBracket) => break,
94      None => {
95        parser.exit_container();
96        return Err(
97          parser
98            .scanner
99            .create_error_for_current_token(ParseErrorKind::UnterminatedArray),
100        );
101      }
102      Some(value_token) => {
103        elements.push(parse_value(parser, value_token)?);
104      }
105    }
106    token = parser.scan_array_comma()?;
107  }
108
109  parser.exit_container();
110  Ok(JsonValue::Array(JsonArray::new(elements)))
111}
112
113#[cfg(test)]
114mod tests {
115  use crate::errors::ParseErrorKind;
116
117  use super::*;
118  use std::borrow::Cow;
119
120  #[test]
121  fn it_should_parse_object() {
122    let value = parse_to_value(
123      r#"{
124    "a": null,
125    "b": [null, "text"],
126    "c": true,
127    d: 25.55
128}"#,
129      &Default::default(),
130    )
131    .unwrap()
132    .unwrap();
133
134    let mut object_map = Map::default();
135    object_map.insert(Cow::Borrowed("a"), JsonValue::Null);
136    object_map.insert(
137      Cow::Borrowed("b"),
138      JsonValue::Array(vec![JsonValue::Null, JsonValue::String(Cow::Borrowed("text"))].into()),
139    );
140    object_map.insert(Cow::Borrowed("c"), JsonValue::Boolean(true));
141    object_map.insert(Cow::Borrowed("d"), JsonValue::Number("25.55"));
142    assert_eq!(value, JsonValue::Object(object_map.into()));
143  }
144
145  #[test]
146  fn it_should_parse_boolean_false() {
147    let value = parse_to_value("false", &Default::default()).unwrap().unwrap();
148    assert_eq!(value, JsonValue::Boolean(false));
149    let value = parse_to_value("true", &Default::default()).unwrap().unwrap();
150    assert_eq!(value, JsonValue::Boolean(true));
151  }
152
153  #[test]
154  fn it_should_parse_boolean_true() {
155    let value = parse_to_value("true", &Default::default()).unwrap().unwrap();
156    assert_eq!(value, JsonValue::Boolean(true));
157  }
158
159  #[test]
160  fn it_should_parse_number() {
161    let value = parse_to_value("50", &Default::default()).unwrap().unwrap();
162    assert_eq!(value, JsonValue::Number("50"));
163  }
164
165  #[test]
166  fn it_should_parse_string() {
167    let value = parse_to_value(r#""test""#, &Default::default()).unwrap().unwrap();
168    assert_eq!(value, JsonValue::String(Cow::Borrowed("test")));
169  }
170
171  #[test]
172  fn it_should_parse_string_with_quotes() {
173    let value = parse_to_value(r#""echo \"test\"""#, &Default::default())
174      .unwrap()
175      .unwrap();
176    assert_eq!(value, JsonValue::String(Cow::Borrowed(r#"echo "test""#)));
177  }
178
179  #[test]
180  fn it_should_parse_array() {
181    let value = parse_to_value(r#"[false, true]"#, &Default::default())
182      .unwrap()
183      .unwrap();
184    assert_eq!(
185      value,
186      JsonValue::Array(vec![JsonValue::Boolean(false), JsonValue::Boolean(true)].into())
187    );
188  }
189
190  #[test]
191  fn it_should_parse_null() {
192    let value = parse_to_value("null", &Default::default()).unwrap().unwrap();
193    assert_eq!(value, JsonValue::Null);
194  }
195
196  #[test]
197  fn it_should_parse_empty() {
198    let value = parse_to_value("", &Default::default()).unwrap();
199    assert!(value.is_none());
200  }
201
202  #[test]
203  fn error_unexpected_token() {
204    let err = parse_to_value("{\n  \"a\":\u{200b}5 }", &Default::default())
205      .err()
206      .unwrap();
207    assert_eq!(err.range().start, 8);
208    assert_eq!(err.range().end, 11);
209    assert!(matches!(err.kind(), ParseErrorKind::UnexpectedToken));
210  }
211
212  #[test]
213  fn error_unexpected_word_should_have_full_range() {
214    // https://github.com/dprint/jsonc-parser/issues/2
215    // the error range should cover the entire word, not just the first character
216    let err = parse_to_value(r#"{ "test": asdf }"#, &Default::default())
217      .err()
218      .unwrap();
219    assert!(matches!(err.kind(), ParseErrorKind::UnexpectedWord));
220    // "asdf" is at bytes 10..14
221    assert_eq!(err.range().start, 10);
222    assert_eq!(err.range().end, 14);
223  }
224
225  #[test]
226  fn it_should_parse_surrogate_pair() {
227    // RFC 8259 ยง 7: non-BMP character ๐„ž (U+1D11E) should be escaped as surrogate pair \uD834\uDD1E
228    let src = r#""\uD834\uDD1E""#;
229    let v = parse_to_value(src, &Default::default()).unwrap().unwrap();
230    if let JsonValue::String(s) = v {
231      assert_eq!("\u{1D11E}", s.as_ref());
232    } else {
233      panic!("Expected string value, got {:?}", v);
234    }
235  }
236
237  #[test]
238  fn it_should_parse_multiple_surrogate_pairs() {
239    let src = r#""\uD834\uDD1E\uD834\uDD1E""#;
240    let v = parse_to_value(src, &Default::default()).unwrap().unwrap();
241    if let JsonValue::String(s) = v {
242      assert_eq!("\u{1D11E}\u{1D11E}", s.as_ref());
243    } else {
244      panic!("Expected string value, got {:?}", v);
245    }
246  }
247
248  #[test]
249  fn it_should_parse_mixed_escapes_with_surrogate_pairs() {
250    // "A๐„žB" where ๐„ž is encoded as surrogate pair
251    let src = r#""\u0041\uD834\uDD1E\u0042""#;
252    let v = parse_to_value(src, &Default::default()).unwrap().unwrap();
253    if let JsonValue::String(s) = v {
254      assert_eq!("A\u{1D11E}B", s.as_ref());
255    } else {
256      panic!("Expected string value, got {:?}", v);
257    }
258  }
259
260  #[test]
261  fn it_should_error_on_unpaired_high_surrogate_with_text() {
262    let src = r#""\uD834x""#;
263    let err = parse_to_value(src, &Default::default()).err().unwrap();
264    assert!(err.to_string().contains("unpaired high surrogate"));
265  }
266
267  #[test]
268  fn it_should_error_on_unpaired_high_surrogate_at_eof() {
269    let src = r#""\uD834""#;
270    let err = parse_to_value(src, &Default::default()).err().unwrap();
271    assert!(err.to_string().contains("unpaired high surrogate"));
272  }
273
274  #[test]
275  fn it_should_error_on_high_surrogate_followed_by_non_low_surrogate() {
276    let src = r#""\uD834\u0041""#;
277    let err = parse_to_value(src, &Default::default()).err().unwrap();
278    assert!(err.to_string().contains("not followed by low surrogate"));
279  }
280
281  #[test]
282  fn it_should_error_on_unpaired_low_surrogate() {
283    // This test verifies existing behavior is maintained
284    let src = r#""\uDC00""#;
285    let err = parse_to_value(src, &Default::default()).err().unwrap();
286    assert!(err.to_string().contains("unpaired low surrogate"));
287  }
288
289  #[test]
290  fn it_should_error_when_arrays_are_deeply_nested() {
291    // Deeply nested arrays cause a stack overflow when recursion depth is not limited
292    let mut json = String::new();
293    let depth = 30_000;
294
295    for _ in 0..depth {
296      json += "[";
297    }
298
299    for _ in 0..depth {
300      json += "]";
301    }
302
303    let result = parse_to_value(&json, &ParseOptions::default());
304
305    match result {
306      Ok(_) => panic!("Expected error, but did not find one."),
307      Err(err) => assert_eq!(err.to_string(), "Maximum nesting depth exceeded on line 1 column 513"),
308    }
309  }
310
311  #[test]
312  fn it_should_error_when_objects_are_deeply_nested() {
313    // Deeply nested objects cause a stack overflow when recursion depth is not limited
314    let mut json = String::new();
315    let depth = 30_000;
316
317    for _ in 0..depth {
318      json += "{\"q\":";
319    }
320
321    for _ in 0..depth {
322      json += "}";
323    }
324
325    let result = parse_to_value(&json, &ParseOptions::default());
326
327    match result {
328      Ok(_) => panic!("Expected error, but did not find one."),
329      Err(err) => assert_eq!(err.to_string(), "Maximum nesting depth exceeded on line 1 column 2561"),
330    }
331  }
332
333  // error cases
334
335  #[track_caller]
336  fn assert_has_error(text: &str, message: &str) {
337    let result = parse_to_value(text, &Default::default());
338    match result {
339      Ok(_) => panic!("Expected error, but did not find one."),
340      Err(err) => assert_eq!(err.to_string(), message),
341    }
342  }
343
344  #[track_caller]
345  fn assert_has_strict_error(text: &str, message: &str) {
346    let result = parse_to_value(text, &strict_options());
347    match result {
348      Ok(_) => panic!("Expected error, but did not find one."),
349      Err(err) => assert_eq!(err.to_string(), message),
350    }
351  }
352
353  fn strict_options() -> ParseOptions {
354    ParseOptions {
355      allow_comments: false,
356      allow_loose_object_property_names: false,
357      allow_trailing_commas: false,
358      allow_missing_commas: false,
359      allow_single_quoted_strings: false,
360      allow_hexadecimal_numbers: false,
361      allow_unary_plus_numbers: false,
362      allow_bare_decimal_point_numbers: false,
363      allow_non_finite_numbers: false,
364      allow_extended_string_escapes: false,
365    }
366  }
367
368  #[test]
369  fn it_should_error_when_has_multiple_values() {
370    assert_has_error(
371      "[][]",
372      "Text cannot contain more than one JSON value on line 1 column 3",
373    );
374  }
375
376  #[test]
377  fn it_should_error_when_object_is_not_terminated() {
378    assert_has_error("{", "Unterminated object on line 1 column 2");
379  }
380
381  #[test]
382  fn it_should_error_when_object_has_unexpected_token() {
383    assert_has_error("{ [] }", "Unexpected token in object on line 1 column 3");
384  }
385
386  #[test]
387  fn it_should_error_when_object_has_two_non_string_tokens() {
388    assert_has_error(
389      "{ asdf asdf: 5 }",
390      "Expected colon after the string or word in object property on line 1 column 8",
391    );
392  }
393
394  #[test]
395  fn it_should_error_when_array_is_not_terminated() {
396    assert_has_error("[", "Unterminated array on line 1 column 2");
397  }
398
399  #[test]
400  fn it_should_error_when_array_has_unexpected_token() {
401    assert_has_error("[:]", "Unexpected colon on line 1 column 2");
402  }
403
404  #[test]
405  fn it_should_error_when_comment_block_not_closed() {
406    assert_has_error("/* test", "Unterminated comment block on line 1 column 1");
407  }
408
409  #[test]
410  fn it_should_error_when_string_lit_not_closed() {
411    assert_has_error("\" test", "Unterminated string literal on line 1 column 1");
412  }
413
414  #[test]
415  fn strict_should_error_object_trailing_comma() {
416    assert_has_strict_error(
417      r#"{ "test": 5, }"#,
418      "Trailing commas are not allowed on line 1 column 12",
419    );
420  }
421
422  #[test]
423  fn strict_should_error_array_trailing_comma() {
424    assert_has_strict_error(r#"[ "test", ]"#, "Trailing commas are not allowed on line 1 column 9");
425  }
426
427  #[test]
428  fn strict_should_error_comment_line() {
429    assert_has_strict_error(r#"[ "test" ] // 1"#, "Comments are not allowed on line 1 column 12");
430  }
431
432  #[test]
433  fn strict_should_error_comment_block() {
434    assert_has_strict_error(r#"[ "test" /* 1 */]"#, "Comments are not allowed on line 1 column 10");
435  }
436
437  #[test]
438  fn strict_should_error_word_property() {
439    assert_has_strict_error(
440      r#"{ word: 5 }"#,
441      "Expected string for object property on line 1 column 3",
442    );
443  }
444
445  #[test]
446  fn strict_should_error_single_quoted_string() {
447    assert_has_strict_error(
448      r#"{ "key": 'value' }"#,
449      "Single-quoted strings are not allowed on line 1 column 10",
450    );
451  }
452
453  #[test]
454  fn strict_should_error_hexadecimal_number() {
455    assert_has_strict_error(
456      r#"{ "key": 0xFF }"#,
457      "Hexadecimal numbers are not allowed on line 1 column 10",
458    );
459  }
460
461  #[test]
462  fn strict_should_error_unary_plus_number() {
463    assert_has_strict_error(
464      r#"{ "key": +42 }"#,
465      "Unary plus on numbers is not allowed on line 1 column 10",
466    );
467  }
468
469  #[test]
470  fn missing_comma_between_properties() {
471    let text = r#"{
472  "name": "alice"
473  "age": 25
474}"#;
475    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
476    let obj = match &value {
477      JsonValue::Object(o) => o,
478      _ => panic!("Expected object"),
479    };
480    assert_eq!(obj.get_number("age").unwrap(), "25");
481
482    // but is strict when strict
483    assert_has_strict_error(text, "Expected comma on line 2 column 18");
484  }
485
486  #[test]
487  fn missing_comma_with_comment_between_properties() {
488    // when comments are allowed but missing commas are not,
489    // should still detect the missing comma after the comment is skipped
490    let result = parse_to_value(
491      r#"{
492  "name": "alice" // comment here
493  "age": 25
494}"#,
495      &ParseOptions {
496        allow_comments: true,
497        allow_missing_commas: false,
498        ..Default::default()
499      },
500    );
501    match result {
502      Ok(_) => panic!("Expected error, but did not find one."),
503      Err(err) => assert_eq!(err.to_string(), "Expected comma on line 2 column 18"),
504    }
505  }
506
507  #[test]
508  fn missing_comma_between_array_elements() {
509    let text = "[1 2]";
510    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
511    assert_eq!(
512      value,
513      JsonValue::Array(vec![JsonValue::Number("1"), JsonValue::Number("2")].into())
514    );
515
516    // but is strict when strict
517    assert_has_strict_error(text, "Expected comma on line 1 column 3");
518    assert_has_strict_error("[01]", "Expected comma on line 1 column 3");
519    assert_has_strict_error(r#"["a" "b"]"#, "Expected comma on line 1 column 5");
520    assert_has_strict_error("[true false]", "Expected comma on line 1 column 6");
521    assert_has_strict_error("[null null]", "Expected comma on line 1 column 6");
522    assert_has_strict_error("[[1] [2]]", "Expected comma on line 1 column 5");
523    assert_has_strict_error(r#"[{"a":1} {"b":2}]"#, "Expected comma on line 1 column 9");
524    assert_has_strict_error("[1 2", "Expected comma on line 1 column 3");
525
526    // these are not missing commas
527    assert_has_strict_error("[1", "Unterminated array on line 1 column 3");
528    assert_has_strict_error("[1 a ]", "Unexpected word on line 1 column 4");
529
530    for text in ["[]", "[ ]", "[1,2]", "[1 , 2]", "[[1],[2]]", r#"[{"a":1},{"b":2}]"#] {
531      parse_to_value(text, &strict_options()).unwrap();
532    }
533  }
534
535  #[test]
536  fn missing_comma_not_allowed_with_trailing_commas_allowed_in_array() {
537    let options = ParseOptions {
538      allow_missing_commas: false,
539      allow_trailing_commas: true,
540      ..Default::default()
541    };
542    for text in ["[]", "[1,]", "[1, 2,]", "[[1,],[2,],]"] {
543      parse_to_value(text, &options).unwrap();
544    }
545  }
546
547  #[test]
548  fn missing_comma_with_comment_between_array_elements() {
549    // when comments are allowed but missing commas are not,
550    // should still detect the missing comma after the comment is skipped
551    let result = parse_to_value(
552      r#"[
553  1 // comment here
554  2
555]"#,
556      &ParseOptions {
557        allow_comments: true,
558        allow_missing_commas: false,
559        ..Default::default()
560      },
561    );
562    match result {
563      Ok(_) => panic!("Expected error, but did not find one."),
564      Err(err) => assert_eq!(err.to_string(), "Expected comma on line 2 column 4"),
565    }
566  }
567
568  #[test]
569  fn it_should_parse_unquoted_keys_with_hex_and_trailing_comma() {
570    let text = r#"{
571      CP_CanFuncReqId: 0x7DF,  // 2015
572  }"#;
573    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
574    let obj = match &value {
575      JsonValue::Object(o) => o,
576      _ => panic!("Expected object"),
577    };
578    assert_eq!(obj.get_number("CP_CanFuncReqId").unwrap(), "0x7DF");
579  }
580
581  #[test]
582  fn it_should_parse_keywords_as_loose_property_names() {
583    let value = parse_to_value(r#"{ true: 1, false: 2 null: 3 }"#, &Default::default())
584      .unwrap()
585      .unwrap();
586    let obj = match &value {
587      JsonValue::Object(o) => o,
588      _ => panic!("Expected object"),
589    };
590    assert_eq!(obj.get_number("true").unwrap(), "1");
591    assert_eq!(obj.get_number("false").unwrap(), "2");
592    assert_eq!(obj.get_number("null").unwrap(), "3");
593  }
594
595  #[test]
596  fn it_should_parse_loose_property_names_with_dollar_signs() {
597    let value = parse_to_value(
598      r#"{ $: 1, _$_: 2, $_$hello123: 3, NaN$: 4, true$: 5 }"#,
599      &Default::default(),
600    )
601    .unwrap()
602    .unwrap();
603    let obj = match &value {
604      JsonValue::Object(o) => o,
605      _ => panic!("Expected object"),
606    };
607    assert_eq!(obj.get_number("$").unwrap(), "1");
608    assert_eq!(obj.get_number("_$_").unwrap(), "2");
609    assert_eq!(obj.get_number("$_$hello123").unwrap(), "3");
610    assert_eq!(obj.get_number("NaN$").unwrap(), "4");
611    assert_eq!(obj.get_number("true$").unwrap(), "5");
612  }
613
614  #[test]
615  fn it_should_parse_loose_property_names_starting_with_infinity_or_nan() {
616    let value = parse_to_value(r#"{ Infinity_count: 1, NaN-key: 2 }"#, &Default::default())
617      .unwrap()
618      .unwrap();
619    let obj = match &value {
620      JsonValue::Object(o) => o,
621      _ => panic!("Expected object"),
622    };
623    assert_eq!(obj.get_number("Infinity_count").unwrap(), "1");
624    assert_eq!(obj.get_number("NaN-key").unwrap(), "2");
625  }
626
627  #[test]
628  fn it_should_keep_keyword_boundaries_with_missing_commas() {
629    let value = parse_to_value("[true-1]", &Default::default()).unwrap().unwrap();
630    let arr = match value {
631      JsonValue::Array(a) => a,
632      _ => panic!("Expected array"),
633    };
634    assert_eq!(arr.len(), 2);
635    assert!(matches!(arr.get(0), Some(JsonValue::Boolean(true))));
636    assert!(matches!(arr.get(1), Some(JsonValue::Number("-1"))));
637  }
638
639  #[test]
640  fn it_should_parse_unary_plus_numbers() {
641    let value = parse_to_value(r#"{ "test": +42 }"#, &Default::default())
642      .unwrap()
643      .unwrap();
644    let obj = match &value {
645      JsonValue::Object(o) => o,
646      _ => panic!("Expected object"),
647    };
648    assert_eq!(obj.get_number("test").unwrap(), "+42");
649  }
650
651  #[test]
652  fn it_should_parse_large_shallow_objects() {
653    // makes sure that nesting depth limit does not affect shallow objects
654    let mut json = "{\"q\":[".to_string();
655    let size = 1_000;
656
657    for _ in 0..size {
658      json += "{\"q\":[{}]}, [\"hello\"], ";
659    }
660
661    json += "]}";
662
663    let result = parse_to_value(&json, &ParseOptions::default());
664    assert!(result.is_ok());
665  }
666}