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    }
363  }
364
365  #[test]
366  fn it_should_error_when_has_multiple_values() {
367    assert_has_error(
368      "[][]",
369      "Text cannot contain more than one JSON value on line 1 column 3",
370    );
371  }
372
373  #[test]
374  fn it_should_error_when_object_is_not_terminated() {
375    assert_has_error("{", "Unterminated object on line 1 column 2");
376  }
377
378  #[test]
379  fn it_should_error_when_object_has_unexpected_token() {
380    assert_has_error("{ [] }", "Unexpected token in object on line 1 column 3");
381  }
382
383  #[test]
384  fn it_should_error_when_object_has_two_non_string_tokens() {
385    assert_has_error(
386      "{ asdf asdf: 5 }",
387      "Expected colon after the string or word in object property on line 1 column 8",
388    );
389  }
390
391  #[test]
392  fn it_should_error_when_array_is_not_terminated() {
393    assert_has_error("[", "Unterminated array on line 1 column 2");
394  }
395
396  #[test]
397  fn it_should_error_when_array_has_unexpected_token() {
398    assert_has_error("[:]", "Unexpected colon on line 1 column 2");
399  }
400
401  #[test]
402  fn it_should_error_when_comment_block_not_closed() {
403    assert_has_error("/* test", "Unterminated comment block on line 1 column 1");
404  }
405
406  #[test]
407  fn it_should_error_when_string_lit_not_closed() {
408    assert_has_error("\" test", "Unterminated string literal on line 1 column 1");
409  }
410
411  #[test]
412  fn strict_should_error_object_trailing_comma() {
413    assert_has_strict_error(
414      r#"{ "test": 5, }"#,
415      "Trailing commas are not allowed on line 1 column 12",
416    );
417  }
418
419  #[test]
420  fn strict_should_error_array_trailing_comma() {
421    assert_has_strict_error(r#"[ "test", ]"#, "Trailing commas are not allowed on line 1 column 9");
422  }
423
424  #[test]
425  fn strict_should_error_comment_line() {
426    assert_has_strict_error(r#"[ "test" ] // 1"#, "Comments are not allowed on line 1 column 12");
427  }
428
429  #[test]
430  fn strict_should_error_comment_block() {
431    assert_has_strict_error(r#"[ "test" /* 1 */]"#, "Comments are not allowed on line 1 column 10");
432  }
433
434  #[test]
435  fn strict_should_error_word_property() {
436    assert_has_strict_error(
437      r#"{ word: 5 }"#,
438      "Expected string for object property on line 1 column 3",
439    );
440  }
441
442  #[test]
443  fn strict_should_error_single_quoted_string() {
444    assert_has_strict_error(
445      r#"{ "key": 'value' }"#,
446      "Single-quoted strings are not allowed on line 1 column 10",
447    );
448  }
449
450  #[test]
451  fn strict_should_error_hexadecimal_number() {
452    assert_has_strict_error(
453      r#"{ "key": 0xFF }"#,
454      "Hexadecimal numbers are not allowed on line 1 column 10",
455    );
456  }
457
458  #[test]
459  fn strict_should_error_unary_plus_number() {
460    assert_has_strict_error(
461      r#"{ "key": +42 }"#,
462      "Unary plus on numbers is not allowed on line 1 column 10",
463    );
464  }
465
466  #[test]
467  fn missing_comma_between_properties() {
468    let text = r#"{
469  "name": "alice"
470  "age": 25
471}"#;
472    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
473    let obj = match &value {
474      JsonValue::Object(o) => o,
475      _ => panic!("Expected object"),
476    };
477    assert_eq!(obj.get_number("age").unwrap(), "25");
478
479    // but is strict when strict
480    assert_has_strict_error(text, "Expected comma on line 2 column 18");
481  }
482
483  #[test]
484  fn missing_comma_with_comment_between_properties() {
485    // when comments are allowed but missing commas are not,
486    // should still detect the missing comma after the comment is skipped
487    let result = parse_to_value(
488      r#"{
489  "name": "alice" // comment here
490  "age": 25
491}"#,
492      &ParseOptions {
493        allow_comments: true,
494        allow_missing_commas: false,
495        ..Default::default()
496      },
497    );
498    match result {
499      Ok(_) => panic!("Expected error, but did not find one."),
500      Err(err) => assert_eq!(err.to_string(), "Expected comma on line 2 column 18"),
501    }
502  }
503
504  #[test]
505  fn missing_comma_between_array_elements() {
506    let text = "[1 2]";
507    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
508    assert_eq!(
509      value,
510      JsonValue::Array(vec![JsonValue::Number("1"), JsonValue::Number("2")].into())
511    );
512
513    // but is strict when strict
514    assert_has_strict_error(text, "Expected comma on line 1 column 3");
515    assert_has_strict_error("[01]", "Expected comma on line 1 column 3");
516    assert_has_strict_error(r#"["a" "b"]"#, "Expected comma on line 1 column 5");
517    assert_has_strict_error("[true false]", "Expected comma on line 1 column 6");
518    assert_has_strict_error("[null null]", "Expected comma on line 1 column 6");
519    assert_has_strict_error("[[1] [2]]", "Expected comma on line 1 column 5");
520    assert_has_strict_error(r#"[{"a":1} {"b":2}]"#, "Expected comma on line 1 column 9");
521    assert_has_strict_error("[1 2", "Expected comma on line 1 column 3");
522
523    // these are not missing commas
524    assert_has_strict_error("[1", "Unterminated array on line 1 column 3");
525    assert_has_strict_error("[1 a ]", "Unexpected word on line 1 column 4");
526
527    for text in ["[]", "[ ]", "[1,2]", "[1 , 2]", "[[1],[2]]", r#"[{"a":1},{"b":2}]"#] {
528      parse_to_value(text, &strict_options()).unwrap();
529    }
530  }
531
532  #[test]
533  fn missing_comma_not_allowed_with_trailing_commas_allowed_in_array() {
534    let options = ParseOptions {
535      allow_missing_commas: false,
536      allow_trailing_commas: true,
537      ..Default::default()
538    };
539    for text in ["[]", "[1,]", "[1, 2,]", "[[1,],[2,],]"] {
540      parse_to_value(text, &options).unwrap();
541    }
542  }
543
544  #[test]
545  fn missing_comma_with_comment_between_array_elements() {
546    // when comments are allowed but missing commas are not,
547    // should still detect the missing comma after the comment is skipped
548    let result = parse_to_value(
549      r#"[
550  1 // comment here
551  2
552]"#,
553      &ParseOptions {
554        allow_comments: true,
555        allow_missing_commas: false,
556        ..Default::default()
557      },
558    );
559    match result {
560      Ok(_) => panic!("Expected error, but did not find one."),
561      Err(err) => assert_eq!(err.to_string(), "Expected comma on line 2 column 4"),
562    }
563  }
564
565  #[test]
566  fn it_should_parse_unquoted_keys_with_hex_and_trailing_comma() {
567    let text = r#"{
568      CP_CanFuncReqId: 0x7DF,  // 2015
569  }"#;
570    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
571    let obj = match &value {
572      JsonValue::Object(o) => o,
573      _ => panic!("Expected object"),
574    };
575    assert_eq!(obj.get_number("CP_CanFuncReqId").unwrap(), "0x7DF");
576  }
577
578  #[test]
579  fn it_should_parse_unary_plus_numbers() {
580    let value = parse_to_value(r#"{ "test": +42 }"#, &Default::default())
581      .unwrap()
582      .unwrap();
583    let obj = match &value {
584      JsonValue::Object(o) => o,
585      _ => panic!("Expected object"),
586    };
587    assert_eq!(obj.get_number("test").unwrap(), "+42");
588  }
589
590  #[test]
591  fn it_should_parse_large_shallow_objects() {
592    // makes sure that nesting depth limit does not affect shallow objects
593    let mut json = "{\"q\":[".to_string();
594    let size = 1_000;
595
596    for _ in 0..size {
597      json += "{\"q\":[{}]}, [\"hello\"], ";
598    }
599
600    json += "]}";
601
602    let result = parse_to_value(&json, &ParseOptions::default());
603    assert!(result.is_ok());
604  }
605}