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 it_should_parse_surrogate_pair() {
214    // RFC 8259 ยง 7: non-BMP character ๐„ž (U+1D11E) should be escaped as surrogate pair \uD834\uDD1E
215    let src = r#""\uD834\uDD1E""#;
216    let v = parse_to_value(src, &Default::default()).unwrap().unwrap();
217    if let JsonValue::String(s) = v {
218      assert_eq!("\u{1D11E}", s.as_ref());
219    } else {
220      panic!("Expected string value, got {:?}", v);
221    }
222  }
223
224  #[test]
225  fn it_should_parse_multiple_surrogate_pairs() {
226    let src = r#""\uD834\uDD1E\uD834\uDD1E""#;
227    let v = parse_to_value(src, &Default::default()).unwrap().unwrap();
228    if let JsonValue::String(s) = v {
229      assert_eq!("\u{1D11E}\u{1D11E}", s.as_ref());
230    } else {
231      panic!("Expected string value, got {:?}", v);
232    }
233  }
234
235  #[test]
236  fn it_should_parse_mixed_escapes_with_surrogate_pairs() {
237    // "A๐„žB" where ๐„ž is encoded as surrogate pair
238    let src = r#""\u0041\uD834\uDD1E\u0042""#;
239    let v = parse_to_value(src, &Default::default()).unwrap().unwrap();
240    if let JsonValue::String(s) = v {
241      assert_eq!("A\u{1D11E}B", s.as_ref());
242    } else {
243      panic!("Expected string value, got {:?}", v);
244    }
245  }
246
247  #[test]
248  fn it_should_error_on_unpaired_high_surrogate_with_text() {
249    let src = r#""\uD834x""#;
250    let err = parse_to_value(src, &Default::default()).err().unwrap();
251    assert!(err.to_string().contains("unpaired high surrogate"));
252  }
253
254  #[test]
255  fn it_should_error_on_unpaired_high_surrogate_at_eof() {
256    let src = r#""\uD834""#;
257    let err = parse_to_value(src, &Default::default()).err().unwrap();
258    assert!(err.to_string().contains("unpaired high surrogate"));
259  }
260
261  #[test]
262  fn it_should_error_on_high_surrogate_followed_by_non_low_surrogate() {
263    let src = r#""\uD834\u0041""#;
264    let err = parse_to_value(src, &Default::default()).err().unwrap();
265    assert!(err.to_string().contains("not followed by low surrogate"));
266  }
267
268  #[test]
269  fn it_should_error_on_unpaired_low_surrogate() {
270    // This test verifies existing behavior is maintained
271    let src = r#""\uDC00""#;
272    let err = parse_to_value(src, &Default::default()).err().unwrap();
273    assert!(err.to_string().contains("unpaired low surrogate"));
274  }
275
276  #[test]
277  fn it_should_error_when_arrays_are_deeply_nested() {
278    // Deeply nested arrays cause a stack overflow when recursion depth is not limited
279    let mut json = String::new();
280    let depth = 30_000;
281
282    for _ in 0..depth {
283      json += "[";
284    }
285
286    for _ in 0..depth {
287      json += "]";
288    }
289
290    let result = parse_to_value(&json, &ParseOptions::default());
291
292    match result {
293      Ok(_) => panic!("Expected error, but did not find one."),
294      Err(err) => assert_eq!(err.to_string(), "Maximum nesting depth exceeded on line 1 column 513"),
295    }
296  }
297
298  #[test]
299  fn it_should_error_when_objects_are_deeply_nested() {
300    // Deeply nested objects cause a stack overflow when recursion depth is not limited
301    let mut json = String::new();
302    let depth = 30_000;
303
304    for _ in 0..depth {
305      json += "{\"q\":";
306    }
307
308    for _ in 0..depth {
309      json += "}";
310    }
311
312    let result = parse_to_value(&json, &ParseOptions::default());
313
314    match result {
315      Ok(_) => panic!("Expected error, but did not find one."),
316      Err(err) => assert_eq!(err.to_string(), "Maximum nesting depth exceeded on line 1 column 2561"),
317    }
318  }
319
320  // error cases
321
322  #[track_caller]
323  fn assert_has_error(text: &str, message: &str) {
324    let result = parse_to_value(text, &Default::default());
325    match result {
326      Ok(_) => panic!("Expected error, but did not find one."),
327      Err(err) => assert_eq!(err.to_string(), message),
328    }
329  }
330
331  #[track_caller]
332  fn assert_has_strict_error(text: &str, message: &str) {
333    let result = parse_to_value(
334      text,
335      &ParseOptions {
336        allow_comments: false,
337        allow_loose_object_property_names: false,
338        allow_trailing_commas: false,
339        allow_missing_commas: false,
340        allow_single_quoted_strings: false,
341        allow_hexadecimal_numbers: false,
342        allow_unary_plus_numbers: false,
343      },
344    );
345    match result {
346      Ok(_) => panic!("Expected error, but did not find one."),
347      Err(err) => assert_eq!(err.to_string(), message),
348    }
349  }
350
351  #[test]
352  fn it_should_error_when_has_multiple_values() {
353    assert_has_error(
354      "[][]",
355      "Text cannot contain more than one JSON value on line 1 column 3",
356    );
357  }
358
359  #[test]
360  fn it_should_error_when_object_is_not_terminated() {
361    assert_has_error("{", "Unterminated object on line 1 column 2");
362  }
363
364  #[test]
365  fn it_should_error_when_object_has_unexpected_token() {
366    assert_has_error("{ [] }", "Unexpected token in object on line 1 column 3");
367  }
368
369  #[test]
370  fn it_should_error_when_object_has_two_non_string_tokens() {
371    assert_has_error(
372      "{ asdf asdf: 5 }",
373      "Expected colon after the string or word in object property on line 1 column 8",
374    );
375  }
376
377  #[test]
378  fn it_should_error_when_array_is_not_terminated() {
379    assert_has_error("[", "Unterminated array on line 1 column 2");
380  }
381
382  #[test]
383  fn it_should_error_when_array_has_unexpected_token() {
384    assert_has_error("[:]", "Unexpected colon on line 1 column 2");
385  }
386
387  #[test]
388  fn it_should_error_when_comment_block_not_closed() {
389    assert_has_error("/* test", "Unterminated comment block on line 1 column 1");
390  }
391
392  #[test]
393  fn it_should_error_when_string_lit_not_closed() {
394    assert_has_error("\" test", "Unterminated string literal on line 1 column 1");
395  }
396
397  #[test]
398  fn strict_should_error_object_trailing_comma() {
399    assert_has_strict_error(
400      r#"{ "test": 5, }"#,
401      "Trailing commas are not allowed on line 1 column 12",
402    );
403  }
404
405  #[test]
406  fn strict_should_error_array_trailing_comma() {
407    assert_has_strict_error(r#"[ "test", ]"#, "Trailing commas are not allowed on line 1 column 9");
408  }
409
410  #[test]
411  fn strict_should_error_comment_line() {
412    assert_has_strict_error(r#"[ "test" ] // 1"#, "Comments are not allowed on line 1 column 12");
413  }
414
415  #[test]
416  fn strict_should_error_comment_block() {
417    assert_has_strict_error(r#"[ "test" /* 1 */]"#, "Comments are not allowed on line 1 column 10");
418  }
419
420  #[test]
421  fn strict_should_error_word_property() {
422    assert_has_strict_error(
423      r#"{ word: 5 }"#,
424      "Expected string for object property on line 1 column 3",
425    );
426  }
427
428  #[test]
429  fn strict_should_error_single_quoted_string() {
430    assert_has_strict_error(
431      r#"{ "key": 'value' }"#,
432      "Single-quoted strings are not allowed on line 1 column 10",
433    );
434  }
435
436  #[test]
437  fn strict_should_error_hexadecimal_number() {
438    assert_has_strict_error(
439      r#"{ "key": 0xFF }"#,
440      "Hexadecimal numbers are not allowed on line 1 column 10",
441    );
442  }
443
444  #[test]
445  fn strict_should_error_unary_plus_number() {
446    assert_has_strict_error(
447      r#"{ "key": +42 }"#,
448      "Unary plus on numbers is not allowed on line 1 column 10",
449    );
450  }
451
452  #[test]
453  fn missing_comma_between_properties() {
454    let text = r#"{
455  "name": "alice"
456  "age": 25
457}"#;
458    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
459    let obj = match &value {
460      JsonValue::Object(o) => o,
461      _ => panic!("Expected object"),
462    };
463    assert_eq!(obj.get_number("age").unwrap(), "25");
464
465    // but is strict when strict
466    assert_has_strict_error(text, "Expected comma on line 2 column 18");
467  }
468
469  #[test]
470  fn missing_comma_with_comment_between_properties() {
471    // when comments are allowed but missing commas are not,
472    // should still detect the missing comma after the comment is skipped
473    let result = parse_to_value(
474      r#"{
475  "name": "alice" // comment here
476  "age": 25
477}"#,
478      &ParseOptions {
479        allow_comments: true,
480        allow_missing_commas: false,
481        ..Default::default()
482      },
483    );
484    match result {
485      Ok(_) => panic!("Expected error, but did not find one."),
486      Err(err) => assert_eq!(err.to_string(), "Expected comma on line 2 column 18"),
487    }
488  }
489
490  #[test]
491  fn it_should_parse_unquoted_keys_with_hex_and_trailing_comma() {
492    let text = r#"{
493      CP_CanFuncReqId: 0x7DF,  // 2015
494  }"#;
495    let value = parse_to_value(text, &Default::default()).unwrap().unwrap();
496    let obj = match &value {
497      JsonValue::Object(o) => o,
498      _ => panic!("Expected object"),
499    };
500    assert_eq!(obj.get_number("CP_CanFuncReqId").unwrap(), "0x7DF");
501  }
502
503  #[test]
504  fn it_should_parse_unary_plus_numbers() {
505    let value = parse_to_value(r#"{ "test": +42 }"#, &Default::default())
506      .unwrap()
507      .unwrap();
508    let obj = match &value {
509      JsonValue::Object(o) => o,
510      _ => panic!("Expected object"),
511    };
512    assert_eq!(obj.get_number("test").unwrap(), "+42");
513  }
514
515  #[test]
516  fn it_should_parse_large_shallow_objects() {
517    // makes sure that nesting depth limit does not affect shallow objects
518    let mut json = "{\"q\":[".to_string();
519    let size = 1_000;
520
521    for _ in 0..size {
522      json += "{\"q\":[{}]}, [\"hello\"], ";
523    }
524
525    json += "]}";
526
527    let result = parse_to_value(&json, &ParseOptions::default());
528    assert!(result.is_ok());
529  }
530}