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