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::parser::JsoncParser;
6use crate::value::Map;
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::new();
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_string();
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::new();
135    object_map.insert(String::from("a"), JsonValue::Null);
136    object_map.insert(
137      String::from("b"),
138      JsonValue::Array(vec![JsonValue::Null, JsonValue::String(Cow::Borrowed("text"))].into()),
139    );
140    object_map.insert(String::from("c"), JsonValue::Boolean(true));
141    object_map.insert(String::from("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(
347      text,
348      &ParseOptions {
349        allow_comments: false,
350        allow_loose_object_property_names: false,
351        allow_trailing_commas: false,
352        allow_missing_commas: false,
353        allow_single_quoted_strings: false,
354        allow_hexadecimal_numbers: false,
355        allow_unary_plus_numbers: false,
356      },
357    );
358    match result {
359      Ok(_) => panic!("Expected error, but did not find one."),
360      Err(err) => assert_eq!(err.to_string(), message),
361    }
362  }
363
364  #[test]
365  fn it_should_error_when_has_multiple_values() {
366    assert_has_error(
367      "[][]",
368      "Text cannot contain more than one JSON value on line 1 column 3",
369    );
370  }
371
372  #[test]
373  fn it_should_error_when_object_is_not_terminated() {
374    assert_has_error("{", "Unterminated object on line 1 column 2");
375  }
376
377  #[test]
378  fn it_should_error_when_object_has_unexpected_token() {
379    assert_has_error("{ [] }", "Unexpected token in object on line 1 column 3");
380  }
381
382  #[test]
383  fn it_should_error_when_object_has_two_non_string_tokens() {
384    assert_has_error(
385      "{ asdf asdf: 5 }",
386      "Expected colon after the string or word in object property on line 1 column 8",
387    );
388  }
389
390  #[test]
391  fn it_should_error_when_array_is_not_terminated() {
392    assert_has_error("[", "Unterminated array on line 1 column 2");
393  }
394
395  #[test]
396  fn it_should_error_when_array_has_unexpected_token() {
397    assert_has_error("[:]", "Unexpected colon on line 1 column 2");
398  }
399
400  #[test]
401  fn it_should_error_when_comment_block_not_closed() {
402    assert_has_error("/* test", "Unterminated comment block on line 1 column 1");
403  }
404
405  #[test]
406  fn it_should_error_when_string_lit_not_closed() {
407    assert_has_error("\" test", "Unterminated string literal on line 1 column 1");
408  }
409
410  #[test]
411  fn strict_should_error_object_trailing_comma() {
412    assert_has_strict_error(
413      r#"{ "test": 5, }"#,
414      "Trailing commas are not allowed on line 1 column 12",
415    );
416  }
417
418  #[test]
419  fn strict_should_error_array_trailing_comma() {
420    assert_has_strict_error(r#"[ "test", ]"#, "Trailing commas are not allowed on line 1 column 9");
421  }
422
423  #[test]
424  fn strict_should_error_comment_line() {
425    assert_has_strict_error(r#"[ "test" ] // 1"#, "Comments are not allowed on line 1 column 12");
426  }
427
428  #[test]
429  fn strict_should_error_comment_block() {
430    assert_has_strict_error(r#"[ "test" /* 1 */]"#, "Comments are not allowed on line 1 column 10");
431  }
432
433  #[test]
434  fn strict_should_error_word_property() {
435    assert_has_strict_error(
436      r#"{ word: 5 }"#,
437      "Expected string for object property on line 1 column 3",
438    );
439  }
440
441  #[test]
442  fn strict_should_error_single_quoted_string() {
443    assert_has_strict_error(
444      r#"{ "key": 'value' }"#,
445      "Single-quoted strings are not allowed on line 1 column 10",
446    );
447  }
448
449  #[test]
450  fn strict_should_error_hexadecimal_number() {
451    assert_has_strict_error(
452      r#"{ "key": 0xFF }"#,
453      "Hexadecimal numbers are not allowed on line 1 column 10",
454    );
455  }
456
457  #[test]
458  fn strict_should_error_unary_plus_number() {
459    assert_has_strict_error(
460      r#"{ "key": +42 }"#,
461      "Unary plus on numbers is not allowed on line 1 column 10",
462    );
463  }
464
465  #[test]
466  fn missing_comma_between_properties() {
467    let text = r#"{
468  "name": "alice"
469  "age": 25
470}"#;
471    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
472    let obj = match &value {
473      JsonValue::Object(o) => o,
474      _ => panic!("Expected object"),
475    };
476    assert_eq!(obj.get_number("age").unwrap(), "25");
477
478    // but is strict when strict
479    assert_has_strict_error(text, "Expected comma on line 2 column 18");
480  }
481
482  #[test]
483  fn missing_comma_with_comment_between_properties() {
484    // when comments are allowed but missing commas are not,
485    // should still detect the missing comma after the comment is skipped
486    let result = parse_to_value(
487      r#"{
488  "name": "alice" // comment here
489  "age": 25
490}"#,
491      &ParseOptions {
492        allow_comments: true,
493        allow_missing_commas: false,
494        ..Default::default()
495      },
496    );
497    match result {
498      Ok(_) => panic!("Expected error, but did not find one."),
499      Err(err) => assert_eq!(err.to_string(), "Expected comma on line 2 column 18"),
500    }
501  }
502
503  #[test]
504  fn it_should_parse_unquoted_keys_with_hex_and_trailing_comma() {
505    let text = r#"{
506      CP_CanFuncReqId: 0x7DF,  // 2015
507  }"#;
508    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
509    let obj = match &value {
510      JsonValue::Object(o) => o,
511      _ => panic!("Expected object"),
512    };
513    assert_eq!(obj.get_number("CP_CanFuncReqId").unwrap(), "0x7DF");
514  }
515
516  #[test]
517  fn it_should_parse_unary_plus_numbers() {
518    let value = parse_to_value(r#"{ "test": +42 }"#, &Default::default())
519      .unwrap()
520      .unwrap();
521    let obj = match &value {
522      JsonValue::Object(o) => o,
523      _ => panic!("Expected object"),
524    };
525    assert_eq!(obj.get_number("test").unwrap(), "+42");
526  }
527
528  #[test]
529  fn it_should_parse_large_shallow_objects() {
530    // makes sure that nesting depth limit does not affect shallow objects
531    let mut json = "{\"q\":[".to_string();
532    let size = 1_000;
533
534    for _ in 0..size {
535      json += "{\"q\":[{}]}, [\"hello\"], ";
536    }
537
538    json += "]}";
539
540    let result = parse_to_value(&json, &ParseOptions::default());
541    assert!(result.is_ok());
542  }
543}