Skip to main content

jsonc_parser/
parse_to_ast.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use super::ast::*;
6use super::common::Range;
7use super::errors::*;
8use super::scanner::Scanner;
9use super::scanner::ScannerOptions;
10use super::tokens::Token;
11use super::tokens::TokenAndRange;
12
13/// Map where the comments are stored in collections where
14/// the key is the previous token end or start of file or
15/// next token start or end of the file.
16pub type CommentMap<'a> = HashMap<usize, Rc<Vec<Comment<'a>>>>;
17
18/// Strategy for handling comments during parsing.
19///
20/// This enum determines how comments in the JSON/JSONC input are collected
21/// and represented in the resulting abstract syntax tree (AST).
22#[derive(Default, Debug, PartialEq, Clone)]
23pub enum CommentCollectionStrategy {
24  /// Comments are not collected and are effectively ignored during parsing.
25  #[default]
26  Off,
27  /// Comments are collected and stored separately from the main AST structure.
28  ///
29  /// When this strategy is used, comments are placed in a [`CommentMap`] where
30  /// the key is the previous token end or start of file, or the next token start
31  /// or end of file.
32  Separate,
33  /// Comments are collected and treated as tokens within the AST.
34  ///
35  /// When this strategy is used, comments appear alongside other tokens in the
36  /// token stream when `tokens: true` is set in [`CollectOptions`].
37  AsTokens,
38}
39
40/// Options for collecting comments and tokens.
41#[derive(Default, Clone)]
42pub struct CollectOptions {
43  /// Include comments in the result.
44  pub comments: CommentCollectionStrategy,
45  /// Include tokens in the result.
46  pub tokens: bool,
47}
48
49/// Options for parsing.
50#[derive(Clone)]
51pub struct ParseOptions {
52  /// Allow comments (defaults to `true`).
53  pub allow_comments: bool,
54  /// Allow words and numbers as object property names (defaults to `true`).
55  pub allow_loose_object_property_names: bool,
56  /// Allow trailing commas on object literal and array literal values (defaults to `true`).
57  pub allow_trailing_commas: bool,
58  /// Allow missing commas between object properties or array elements (defaults to `true`).
59  pub allow_missing_commas: bool,
60  /// Allow single-quoted strings (defaults to `true`).
61  pub allow_single_quoted_strings: bool,
62  /// Allow hexadecimal numbers like 0xFF (defaults to `true`).
63  pub allow_hexadecimal_numbers: bool,
64  /// Allow unary plus sign on numbers like +42 (defaults to `true`).
65  pub allow_unary_plus_numbers: bool,
66  /// Allow a leading or trailing decimal point on numbers like .5 or 5. (defaults to `true`).
67  pub allow_bare_decimal_point_numbers: bool,
68  /// Allow the numbers Infinity, -Infinity and NaN (defaults to `true`).
69  pub allow_non_finite_numbers: bool,
70  /// Allow JSON5 string escapes like \x41, \v and line continuations (defaults to `true`).
71  pub allow_extended_string_escapes: bool,
72}
73
74impl Default for ParseOptions {
75  fn default() -> Self {
76    Self {
77      allow_comments: true,
78      allow_loose_object_property_names: true,
79      allow_trailing_commas: true,
80      allow_missing_commas: true,
81      allow_single_quoted_strings: true,
82      allow_hexadecimal_numbers: true,
83      allow_unary_plus_numbers: true,
84      allow_bare_decimal_point_numbers: true,
85      allow_non_finite_numbers: true,
86      allow_extended_string_escapes: true,
87    }
88  }
89}
90
91/// Result of parsing the text.
92pub struct ParseResult<'a> {
93  /// Collection of comments in the text.
94  ///
95  /// Provide `comments: true` to the `ParseOptions` for this to have a value.
96  ///
97  /// Remarks: The key is the start and end position of the tokens.
98  pub comments: Option<CommentMap<'a>>,
99  /// The JSON value the text contained.
100  pub value: Option<Value<'a>>,
101  /// Collection of tokens (excluding any comments).
102  ///
103  /// Provide `tokens: true` to the `ParseOptions` for this to have a value.
104  pub tokens: Option<Vec<TokenAndRange<'a>>>,
105}
106
107struct Context<'a> {
108  scanner: Scanner<'a>,
109  comments: Option<CommentMap<'a>>,
110  current_comments: Option<Vec<Comment<'a>>>,
111  last_token_end: usize,
112  range_stack: Vec<Range>,
113  tokens: Option<Vec<TokenAndRange<'a>>>,
114  collect_comments_as_tokens: bool,
115  allow_comments: bool,
116  allow_trailing_commas: bool,
117  allow_missing_commas: bool,
118  allow_loose_object_property_names: bool,
119  maximum_nesting_depth: usize,
120}
121
122impl<'a> Context<'a> {
123  pub fn scan(&mut self) -> Result<Option<Token<'a>>, ParseError> {
124    let previous_end = self.last_token_end;
125    let token = self.scan_handling_comments()?;
126    self.last_token_end = self.scanner.token_end();
127
128    // store the comment for the previous token end, and current token start
129    if let Some(comments) = self.comments.as_mut()
130      && let Some(current_comments) = self.current_comments.take()
131    {
132      let current_comments = Rc::new(current_comments);
133      comments.insert(previous_end, current_comments.clone());
134      comments.insert(self.scanner.token_start(), current_comments);
135    }
136
137    if let Some(token) = &token
138      && self.tokens.is_some()
139    {
140      self.capture_token(token.clone());
141    }
142
143    Ok(token)
144  }
145
146  pub fn token(&self) -> Option<Token<'a>> {
147    self.scanner.token()
148  }
149
150  pub fn start_range(&mut self) {
151    self.range_stack.push(Range {
152      start: self.scanner.token_start(),
153      end: 0,
154    });
155  }
156
157  pub fn end_range(&mut self) -> Range {
158    let mut range = self
159      .range_stack
160      .pop()
161      .expect("Range was popped from the stack, but the stack was empty.");
162    range.end = self.scanner.token_end();
163    range
164  }
165
166  pub fn create_range_from_last_token(&self) -> Range {
167    Range {
168      start: self.scanner.token_start(),
169      end: self.scanner.token_end(),
170    }
171  }
172
173  pub fn create_error(&self, kind: ParseErrorKind) -> ParseError {
174    self.scanner.create_error_for_current_token(kind)
175  }
176
177  pub fn create_error_for_current_range(&mut self, kind: ParseErrorKind) -> ParseError {
178    let range = self.end_range();
179    self.create_error_for_range(range, kind)
180  }
181
182  pub fn create_error_for_range(&self, range: Range, kind: ParseErrorKind) -> ParseError {
183    self.scanner.create_error_for_range(range, kind)
184  }
185
186  fn scan_handling_comments(&mut self) -> Result<Option<Token<'a>>, ParseError> {
187    loop {
188      let token = self.scanner.scan()?;
189      match token {
190        Some(token @ Token::CommentLine(_) | token @ Token::CommentBlock(_)) if self.collect_comments_as_tokens => {
191          self.capture_token(token);
192        }
193        Some(Token::CommentLine(text)) => {
194          self.handle_comment(Comment::Line(CommentLine {
195            range: self.create_range_from_last_token(),
196            text,
197          }))?;
198        }
199        Some(Token::CommentBlock(text)) => {
200          self.handle_comment(Comment::Block(CommentBlock {
201            range: self.create_range_from_last_token(),
202            text,
203          }))?;
204        }
205        _ => return Ok(token),
206      }
207    }
208  }
209
210  fn capture_token(&mut self, token: Token<'a>) {
211    let range = self.create_range_from_last_token();
212    if let Some(tokens) = self.tokens.as_mut() {
213      tokens.push(TokenAndRange {
214        token: token.clone(),
215        range,
216      });
217    }
218  }
219
220  fn handle_comment(&mut self, comment: Comment<'a>) -> Result<(), ParseError> {
221    if !self.allow_comments {
222      return Err(self.create_error(ParseErrorKind::CommentsNotAllowed));
223    }
224
225    if self.comments.is_some() {
226      if let Some(comments) = self.current_comments.as_mut() {
227        comments.push(comment);
228      } else {
229        self.current_comments = Some(vec![comment]);
230      }
231    }
232
233    Ok(())
234  }
235}
236
237/// Parses a string containing JSONC to an AST with comments and tokens.
238///
239/// # Example
240///
241/// ```
242/// use jsonc_parser::CollectOptions;
243/// use jsonc_parser::CommentCollectionStrategy;
244/// use jsonc_parser::parse_to_ast;
245/// use jsonc_parser::ParseOptions;
246///
247/// let parse_result = parse_to_ast(r#"{ "test": 5 } // test"#, &CollectOptions {
248///     comments: CommentCollectionStrategy::Separate, // include comments in result
249///     tokens: true, // include tokens in result
250/// }, &Default::default()).expect("Should parse.");
251/// // ...inspect parse_result for value, tokens, and comments here...
252/// ```
253pub fn parse_to_ast<'a>(
254  text: &'a str,
255  collect_options: &CollectOptions,
256  parse_options: &ParseOptions,
257) -> Result<ParseResult<'a>, ParseError> {
258  let mut context = Context {
259    scanner: Scanner::new(
260      text,
261      &ScannerOptions {
262        allow_single_quoted_strings: parse_options.allow_single_quoted_strings,
263        allow_hexadecimal_numbers: parse_options.allow_hexadecimal_numbers,
264        allow_unary_plus_numbers: parse_options.allow_unary_plus_numbers,
265        allow_bare_decimal_point_numbers: parse_options.allow_bare_decimal_point_numbers,
266        allow_non_finite_numbers: parse_options.allow_non_finite_numbers,
267        allow_extended_string_escapes: parse_options.allow_extended_string_escapes,
268      },
269    ),
270    comments: match collect_options.comments {
271      CommentCollectionStrategy::Separate => Some(Default::default()),
272      CommentCollectionStrategy::Off | CommentCollectionStrategy::AsTokens => None,
273    },
274    current_comments: None,
275    last_token_end: 0,
276    range_stack: Vec::new(),
277    tokens: if collect_options.tokens { Some(Vec::new()) } else { None },
278    collect_comments_as_tokens: collect_options.comments == CommentCollectionStrategy::AsTokens,
279    allow_comments: parse_options.allow_comments,
280    allow_trailing_commas: parse_options.allow_trailing_commas,
281    allow_missing_commas: parse_options.allow_missing_commas,
282    allow_loose_object_property_names: parse_options.allow_loose_object_property_names,
283    maximum_nesting_depth: 512,
284  };
285  context.scan()?;
286  let value = parse_value(&mut context)?;
287
288  if context.scan()?.is_some() {
289    return Err(context.create_error(ParseErrorKind::MultipleRootJsonValues));
290  }
291
292  debug_assert!(context.range_stack.is_empty());
293
294  Ok(ParseResult {
295    comments: context.comments,
296    tokens: context.tokens,
297    value,
298  })
299}
300
301fn parse_value<'a>(context: &mut Context<'a>) -> Result<Option<Value<'a>>, ParseError> {
302  if context.range_stack.len() > context.maximum_nesting_depth {
303    return Err(context.create_error_for_current_range(ParseErrorKind::NestingDepthExceeded));
304  }
305
306  match context.token() {
307    None => Ok(None),
308    Some(token) => match token {
309      Token::OpenBrace => Ok(Some(Value::Object(parse_object(context)?))),
310      Token::OpenBracket => Ok(Some(Value::Array(parse_array(context)?))),
311      Token::String(value) => Ok(Some(Value::StringLit(create_string_lit(context, value)))),
312      Token::Boolean(value) => Ok(Some(Value::BooleanLit(create_boolean_lit(context, value)))),
313      Token::Number(value) => Ok(Some(Value::NumberLit(create_number_lit(context, value)))),
314      Token::Null => Ok(Some(Value::NullKeyword(create_null_keyword(context)))),
315      Token::CloseBracket => Err(context.create_error(ParseErrorKind::UnexpectedCloseBracket)),
316      Token::CloseBrace => Err(context.create_error(ParseErrorKind::UnexpectedCloseBrace)),
317      Token::Comma => Err(context.create_error(ParseErrorKind::UnexpectedComma)),
318      Token::Colon => Err(context.create_error(ParseErrorKind::UnexpectedColon)),
319      Token::Word(_) => Err(context.create_error(ParseErrorKind::UnexpectedWord)),
320      Token::CommentLine(_) => unreachable!(),
321      Token::CommentBlock(_) => unreachable!(),
322    },
323  }
324}
325
326fn parse_object<'a>(context: &mut Context<'a>) -> Result<Object<'a>, ParseError> {
327  debug_assert!(context.token() == Some(Token::OpenBrace));
328  let mut properties = Vec::new();
329
330  context.start_range();
331  context.scan()?;
332
333  loop {
334    match context.token() {
335      Some(Token::CloseBrace) => break,
336      Some(Token::String(prop_name)) => {
337        properties.push(parse_object_property(context, PropName::String(prop_name))?);
338      }
339      None => return Err(context.create_error_for_current_range(ParseErrorKind::UnterminatedObject)),
340      Some(token) => match token.as_loose_property_name() {
341        Some(prop_name) => properties.push(parse_object_property(context, PropName::Word(prop_name))?),
342        None => return Err(context.create_error(ParseErrorKind::UnexpectedTokenInObject)),
343      },
344    }
345
346    // skip the comma
347    let after_value_end = context.last_token_end;
348    match context.scan()? {
349      Some(Token::Comma) => {
350        let comma_range = context.create_range_from_last_token();
351        if let Some(Token::CloseBrace) = context.scan()?
352          && !context.allow_trailing_commas
353        {
354          return Err(context.create_error_for_range(comma_range, ParseErrorKind::TrailingCommasNotAllowed));
355        }
356      }
357      Some(Token::String(_) | Token::Word(_) | Token::Number(_) | Token::Boolean(_) | Token::Null)
358        if !context.allow_missing_commas =>
359      {
360        let range = Range {
361          start: after_value_end,
362          end: after_value_end,
363        };
364        return Err(context.create_error_for_range(range, ParseErrorKind::ExpectedComma));
365      }
366      _ => {}
367    }
368  }
369
370  Ok(Object {
371    range: context.end_range(),
372    properties,
373  })
374}
375
376enum PropName<'a> {
377  String(Cow<'a, str>),
378  Word(&'a str),
379}
380
381fn parse_object_property<'a>(context: &mut Context<'a>, prop_name: PropName<'a>) -> Result<ObjectProp<'a>, ParseError> {
382  context.start_range();
383
384  let name = match prop_name {
385    PropName::String(prop_name) => ObjectPropName::String(create_string_lit(context, prop_name)),
386    PropName::Word(prop_name) => {
387      if context.allow_loose_object_property_names {
388        ObjectPropName::Word(create_word(context, prop_name))
389      } else {
390        return Err(context.create_error(ParseErrorKind::ExpectedStringObjectProperty));
391      }
392    }
393  };
394
395  match context.scan()? {
396    Some(Token::Colon) => {}
397    _ => return Err(context.create_error(ParseErrorKind::ExpectedColonAfterObjectKey)),
398  }
399
400  context.scan()?;
401  let value = parse_value(context)?;
402
403  match value {
404    Some(value) => Ok(ObjectProp {
405      range: context.end_range(),
406      name,
407      value,
408    }),
409    None => Err(context.create_error(ParseErrorKind::ExpectedObjectValue)),
410  }
411}
412
413fn parse_array<'a>(context: &mut Context<'a>) -> Result<Array<'a>, ParseError> {
414  debug_assert!(context.token() == Some(Token::OpenBracket));
415  let mut elements = Vec::new();
416
417  context.start_range();
418  context.scan()?;
419
420  loop {
421    match context.token() {
422      Some(Token::CloseBracket) => break,
423      None => return Err(context.create_error_for_current_range(ParseErrorKind::UnterminatedArray)),
424      _ => match parse_value(context)? {
425        Some(value) => elements.push(value),
426        None => return Err(context.create_error_for_current_range(ParseErrorKind::UnterminatedArray)),
427      },
428    }
429
430    // skip the comma
431    let after_value_end = context.last_token_end;
432    match context.scan()? {
433      Some(Token::Comma) => {
434        let comma_range = context.create_range_from_last_token();
435        if let Some(Token::CloseBracket) = context.scan()?
436          && !context.allow_trailing_commas
437        {
438          return Err(context.create_error_for_range(comma_range, ParseErrorKind::TrailingCommasNotAllowed));
439        }
440      }
441      Some(token) if !context.allow_missing_commas && token.is_value_start() => {
442        let range = Range {
443          start: after_value_end,
444          end: after_value_end,
445        };
446        return Err(context.create_error_for_range(range, ParseErrorKind::ExpectedComma));
447      }
448      _ => {}
449    }
450  }
451
452  Ok(Array {
453    range: context.end_range(),
454    elements,
455  })
456}
457
458// factory functions
459
460fn create_string_lit<'a>(context: &Context<'a>, value: Cow<'a, str>) -> StringLit<'a> {
461  StringLit {
462    range: context.create_range_from_last_token(),
463    value,
464  }
465}
466
467fn create_word<'a>(context: &Context<'a>, value: &'a str) -> WordLit<'a> {
468  WordLit {
469    range: context.create_range_from_last_token(),
470    value,
471  }
472}
473
474fn create_boolean_lit(context: &Context, value: bool) -> BooleanLit {
475  BooleanLit {
476    range: context.create_range_from_last_token(),
477    value,
478  }
479}
480
481fn create_number_lit<'a>(context: &Context<'a>, value: &'a str) -> NumberLit<'a> {
482  NumberLit {
483    range: context.create_range_from_last_token(),
484    value,
485  }
486}
487
488fn create_null_keyword(context: &Context) -> NullKeyword {
489  NullKeyword {
490    range: context.create_range_from_last_token(),
491  }
492}
493
494#[cfg(test)]
495mod tests {
496  use super::*;
497  use pretty_assertions::assert_eq;
498
499  #[test]
500  fn it_should_error_when_has_multiple_values() {
501    assert_has_error(
502      "[][]",
503      "Text cannot contain more than one JSON value on line 1 column 3",
504    );
505  }
506
507  #[test]
508  fn it_should_error_when_object_is_not_terminated() {
509    assert_has_error("{", "Unterminated object on line 1 column 1");
510  }
511
512  #[test]
513  fn it_should_error_when_object_has_unexpected_token() {
514    assert_has_error("{ [] }", "Unexpected token in object on line 1 column 3");
515  }
516
517  #[test]
518  fn it_should_error_when_object_has_two_non_string_tokens() {
519    assert_has_error(
520      "{ asdf asdf: 5 }",
521      "Expected colon after the string or word in object property on line 1 column 8",
522    );
523  }
524
525  #[test]
526  fn it_should_error_when_array_is_not_terminated() {
527    assert_has_error("[", "Unterminated array on line 1 column 1");
528  }
529
530  #[test]
531  fn it_should_error_when_array_has_unexpected_token() {
532    assert_has_error("[:]", "Unexpected colon on line 1 column 2");
533  }
534
535  #[test]
536  fn it_should_error_when_comment_block_not_closed() {
537    assert_has_error("/* test", "Unterminated comment block on line 1 column 1");
538  }
539
540  #[test]
541  fn it_should_error_when_string_lit_not_closed() {
542    assert_has_error("\" test", "Unterminated string literal on line 1 column 1");
543  }
544
545  fn assert_has_error(text: &str, message: &str) {
546    let result = parse_to_ast(text, &Default::default(), &Default::default());
547    match result {
548      Ok(_) => panic!("Expected error, but did not find one."),
549      Err(err) => assert_eq!(err.to_string(), message),
550    }
551  }
552
553  #[test]
554  fn strict_should_error_object_trailing_comma() {
555    assert_has_strict_error(
556      r#"{ "test": 5, }"#,
557      "Trailing commas are not allowed on line 1 column 12",
558    );
559  }
560
561  #[test]
562  fn strict_should_error_array_trailing_comma() {
563    assert_has_strict_error(r#"[ "test", ]"#, "Trailing commas are not allowed on line 1 column 9");
564  }
565
566  #[test]
567  fn strict_should_error_comment_line() {
568    assert_has_strict_error(r#"[ "test" ] // 1"#, "Comments are not allowed on line 1 column 12");
569  }
570
571  #[test]
572  fn strict_should_error_comment_block() {
573    assert_has_strict_error(r#"[ "test" /* 1 */]"#, "Comments are not allowed on line 1 column 10");
574  }
575
576  #[test]
577  fn strict_should_error_word_property() {
578    assert_has_strict_error(
579      r#"{ word: 5 }"#,
580      "Expected string for object property on line 1 column 3",
581    );
582    assert_has_strict_error(
583      r#"{ true: 5 }"#,
584      "Expected string for object property on line 1 column 3",
585    );
586    assert_has_strict_error(r#"{ "a": 1 null: 2 }"#, "Expected comma on line 1 column 9");
587  }
588
589  #[test]
590  fn strict_should_error_single_quoted_string() {
591    assert_has_strict_error(
592      r#"{ "key": 'value' }"#,
593      "Single-quoted strings are not allowed on line 1 column 10",
594    );
595  }
596
597  #[test]
598  fn strict_should_error_hexadecimal_number() {
599    assert_has_strict_error(
600      r#"{ "key": 0xFF }"#,
601      "Hexadecimal numbers are not allowed on line 1 column 10",
602    );
603  }
604
605  #[test]
606  fn strict_should_error_unary_plus_number() {
607    assert_has_strict_error(
608      r#"{ "key": +42 }"#,
609      "Unary plus on numbers is not allowed on line 1 column 10",
610    );
611  }
612
613  #[test]
614  fn strict_should_error_bare_decimal_point_number() {
615    assert_has_strict_error(
616      r#"{ "key": .5 }"#,
617      "Leading or trailing decimal points on numbers are not allowed on line 1 column 10",
618    );
619    assert_has_strict_error(
620      r#"{ "key": 5. }"#,
621      "Leading or trailing decimal points on numbers are not allowed on line 1 column 10",
622    );
623  }
624
625  #[test]
626  fn strict_should_error_non_finite_number() {
627    assert_has_strict_error(
628      r#"{ "key": -Infinity }"#,
629      "Infinity and NaN are not allowed on line 1 column 10",
630    );
631    assert_has_strict_error(r#"{ "key": NaN }"#, "Unexpected word on line 1 column 10");
632  }
633
634  #[track_caller]
635  fn assert_has_strict_error(text: &str, message: &str) {
636    let result = parse_to_ast(text, &Default::default(), &strict_options());
637    match result {
638      Ok(_) => panic!("Expected error, but did not find one."),
639      Err(err) => assert_eq!(err.to_string(), message),
640    }
641  }
642
643  fn strict_options() -> ParseOptions {
644    ParseOptions {
645      allow_comments: false,
646      allow_loose_object_property_names: false,
647      allow_trailing_commas: false,
648      allow_missing_commas: false,
649      allow_single_quoted_strings: false,
650      allow_hexadecimal_numbers: false,
651      allow_unary_plus_numbers: false,
652      allow_bare_decimal_point_numbers: false,
653      allow_non_finite_numbers: false,
654      allow_extended_string_escapes: false,
655    }
656  }
657
658  #[test]
659  fn it_should_not_include_tokens_by_default() {
660    let result = parse_to_ast("{}", &Default::default(), &Default::default()).unwrap();
661    assert!(result.tokens.is_none());
662  }
663
664  #[test]
665  fn it_should_include_tokens_when_specified() {
666    let result = parse_to_ast(
667      "{}",
668      &CollectOptions {
669        tokens: true,
670        ..Default::default()
671      },
672      &Default::default(),
673    )
674    .unwrap();
675    let tokens = result.tokens.unwrap();
676    assert_eq!(tokens.len(), 2);
677  }
678
679  #[test]
680  fn it_should_not_include_comments_by_default() {
681    let result = parse_to_ast("{}", &Default::default(), &Default::default()).unwrap();
682    assert!(result.comments.is_none());
683  }
684
685  #[test]
686  fn it_should_include_comments_when_specified() {
687    let result = parse_to_ast(
688      "{} // 2",
689      &CollectOptions {
690        comments: CommentCollectionStrategy::Separate,
691        ..Default::default()
692      },
693      &Default::default(),
694    )
695    .unwrap();
696    let comments = result.comments.unwrap();
697    assert_eq!(comments.len(), 2); // for both positions, but it's the same comment
698  }
699
700  #[cfg(not(feature = "error_unicode_width"))]
701  #[test]
702  fn error_correct_line_column_unicode_width() {
703    assert_has_strict_error(r#"["🧑‍🦰", ["#, "Unterminated array on line 1 column 9");
704  }
705
706  #[cfg(feature = "error_unicode_width")]
707  #[test]
708  fn error_correct_line_column_unicode_width() {
709    assert_has_strict_error(r#"["🧑‍🦰", ["#, "Unterminated array on line 1 column 10");
710  }
711
712  #[test]
713  fn it_should_parse_unquoted_keys_with_hex_and_trailing_comma() {
714    let text = r#"{
715      CP_CanFuncReqId: 0x7DF,  // 2015
716  }"#;
717    {
718      let parse_result = parse_to_ast(text, &Default::default(), &Default::default()).unwrap();
719
720      let value = parse_result.value.unwrap();
721      let obj = value.as_object().unwrap();
722      assert_eq!(obj.properties.len(), 1);
723      assert_eq!(obj.properties[0].name.as_str(), "CP_CanFuncReqId");
724
725      let number_value = obj.properties[0].value.as_number_lit().unwrap();
726      assert_eq!(number_value.value, "0x7DF");
727    }
728    #[cfg(feature = "serde")]
729    {
730      let value: serde_json::Value = crate::parse_to_serde_value(text, &Default::default()).unwrap();
731      // hexadecimal numbers are converted to decimal in serde output
732      assert_eq!(
733        value,
734        serde_json::json!({
735          "CP_CanFuncReqId": 2015
736        })
737      );
738    }
739  }
740
741  #[test]
742  fn it_should_parse_unary_plus_numbers() {
743    let result = parse_to_ast(r#"{ "test": +42 }"#, &Default::default(), &Default::default()).unwrap();
744
745    let value = result.value.unwrap();
746    let obj = value.as_object().unwrap();
747    assert_eq!(obj.properties.len(), 1);
748    assert_eq!(obj.properties[0].name.as_str(), "test");
749
750    let number_value = obj.properties[0].value.as_number_lit().unwrap();
751    assert_eq!(number_value.value, "+42");
752  }
753
754  #[test]
755  fn it_should_parse_keywords_as_loose_property_names() {
756    let result = parse_to_ast(
757      r#"{ true: 1, false: null null: true }"#,
758      &Default::default(),
759      &Default::default(),
760    )
761    .unwrap();
762    let value = result.value.unwrap();
763    let names = value
764      .as_object()
765      .unwrap()
766      .properties
767      .iter()
768      .map(|p| p.name.as_str())
769      .collect::<Vec<_>>();
770    assert_eq!(names, vec!["true", "false", "null"]);
771  }
772
773  #[test]
774  fn it_should_parse_json5_numbers_and_keep_non_finite_words_as_keys() {
775    let result = parse_to_ast(
776      r#"{ "a": .5, "b": 5., "c": -Infinity, Infinity: NaN }"#,
777      &Default::default(),
778      &Default::default(),
779    )
780    .unwrap();
781
782    let value = result.value.unwrap();
783    let obj = value.as_object().unwrap();
784    let values = obj
785      .properties
786      .iter()
787      .map(|p| (p.name.as_str(), p.value.as_number_lit().unwrap().value))
788      .collect::<Vec<_>>();
789    assert_eq!(
790      values,
791      vec![("a", ".5"), ("b", "5."), ("c", "-Infinity"), ("Infinity", "NaN")]
792    );
793  }
794
795  #[test]
796  fn missing_comma_between_properties() {
797    let text = r#"{
798  "name": "alice"
799  "age": 25
800}"#;
801    let result = parse_to_ast(text, &Default::default(), &Default::default()).unwrap();
802    assert_eq!(
803      result
804        .value
805        .unwrap()
806        .as_object()
807        .unwrap()
808        .get_number("age")
809        .unwrap()
810        .value,
811      "25"
812    );
813
814    // but is strict when strict
815    assert_has_strict_error(text, "Expected comma on line 2 column 18");
816  }
817
818  #[test]
819  fn missing_comma_with_comment_between_properties() {
820    // when comments are allowed but missing commas are not,
821    // should still detect the missing comma after the comment is skipped
822    let result = parse_to_ast(
823      r#"{
824  "name": "alice" // comment here
825  "age": 25
826}"#,
827      &Default::default(),
828      &ParseOptions {
829        allow_comments: true,
830        allow_missing_commas: false,
831        ..Default::default()
832      },
833    );
834    match result {
835      Ok(_) => panic!("Expected error, but did not find one."),
836      Err(err) => assert_eq!(err.to_string(), "Expected comma on line 2 column 18"),
837    }
838  }
839
840  #[test]
841  fn missing_comma_between_array_elements() {
842    let text = "[1 2]";
843    let result = parse_to_ast(text, &Default::default(), &Default::default()).unwrap();
844    let value = result.value.unwrap();
845    let elements = &value.as_array().unwrap().elements;
846    assert_eq!(elements.len(), 2);
847
848    // but is strict when strict
849    assert_has_strict_error(text, "Expected comma on line 1 column 3");
850    assert_has_strict_error("[01]", "Expected comma on line 1 column 3");
851    assert_has_strict_error(r#"["a" "b"]"#, "Expected comma on line 1 column 5");
852    assert_has_strict_error("[true false]", "Expected comma on line 1 column 6");
853    assert_has_strict_error("[null null]", "Expected comma on line 1 column 6");
854    assert_has_strict_error("[[1] [2]]", "Expected comma on line 1 column 5");
855    assert_has_strict_error(r#"[{"a":1} {"b":2}]"#, "Expected comma on line 1 column 9");
856    assert_has_strict_error("[1 2", "Expected comma on line 1 column 3");
857
858    // these are not missing commas
859    assert_has_strict_error("[1", "Unterminated array on line 1 column 1");
860    assert_has_strict_error("[1 a ]", "Unexpected word on line 1 column 4");
861
862    for text in ["[]", "[ ]", "[1,2]", "[1 , 2]", "[[1],[2]]", r#"[{"a":1},{"b":2}]"#] {
863      parse_to_ast(text, &Default::default(), &strict_options()).unwrap();
864    }
865  }
866
867  #[test]
868  fn missing_comma_not_allowed_with_trailing_commas_allowed_in_array() {
869    let options = ParseOptions {
870      allow_missing_commas: false,
871      allow_trailing_commas: true,
872      ..Default::default()
873    };
874    for text in ["[]", "[1,]", "[1, 2,]", "[[1,],[2,],]"] {
875      parse_to_ast(text, &Default::default(), &options).unwrap();
876    }
877  }
878
879  #[test]
880  fn missing_comma_with_comment_between_array_elements() {
881    // when comments are allowed but missing commas are not,
882    // should still detect the missing comma after the comment is skipped
883    let result = parse_to_ast(
884      r#"[
885  1 // comment here
886  2
887]"#,
888      &Default::default(),
889      &ParseOptions {
890        allow_comments: true,
891        allow_missing_commas: false,
892        ..Default::default()
893      },
894    );
895    match result {
896      Ok(_) => panic!("Expected error, but did not find one."),
897      Err(err) => assert_eq!(err.to_string(), "Expected comma on line 2 column 4"),
898    }
899  }
900
901  #[test]
902  fn it_should_error_when_arrays_are_deeply_nested() {
903    // Deeply nested arrays cause a stack overflow when recursion depth is not limited
904    let mut json = String::new();
905    let depth = 30_000;
906
907    for _ in 0..depth {
908      json += "[";
909    }
910
911    for _ in 0..depth {
912      json += "]";
913    }
914
915    let result = parse_to_ast(&json, &Default::default(), &ParseOptions::default());
916
917    match result {
918      Ok(_) => panic!("Expected error, but did not find one."),
919      Err(err) => assert_eq!(err.to_string(), "Maximum nesting depth exceeded on line 1 column 513"),
920    }
921  }
922
923  #[test]
924  fn it_should_error_when_objects_are_deeply_nested() {
925    // Deeply nested objects cause a stack overflow when recursion depth is not limited
926    let mut json = String::new();
927    let depth = 30_000;
928
929    for _ in 0..depth {
930      json += "{\"q\":";
931    }
932
933    for _ in 0..depth {
934      json += "}";
935    }
936
937    let result = parse_to_ast(&json, &Default::default(), &ParseOptions::default());
938
939    match result {
940      Ok(_) => panic!("Expected error, but did not find one."),
941      Err(err) => assert_eq!(err.to_string(), "Maximum nesting depth exceeded on line 1 column 1282"),
942    }
943  }
944
945  #[test]
946  fn it_should_parse_large_shallow_objects() {
947    // Makes sure that nesting depth limit does not affect shallow objects
948    let mut json = "{\"q\":[".to_string();
949    let size = 1_000;
950
951    for _ in 0..size {
952      json += "{\"q\":[{}]}, [\"hello\"], ";
953    }
954
955    json += "]}";
956
957    let result = parse_to_ast(&json, &Default::default(), &ParseOptions::default());
958
959    match result {
960      Ok(_) => {}
961      Err(_) => panic!("Expected Ok, but did not find one."),
962    }
963  }
964}