1use jsonc_parser::{ParseOptions as JsoncParseOptions, parse_to_serde_value};
2use serde_json::Value as JsonValue;
3
4use crate::format::{FormatOptions, Formatted};
5
6#[derive(Clone, Debug, Default)]
8pub struct JsoncExtraOptions {
9 pub disallow_comments: bool,
10 pub allow_trailing_comma: bool,
11}
12
13pub fn parse_jsonc(
15 text: &str,
16 fmt_options: Option<FormatOptions>,
17 jsonc_options: Option<JsoncExtraOptions>,
18) -> Result<Formatted<JsonValue>, Box<dyn std::error::Error>> {
19 let fmt_opts = fmt_options.unwrap_or_default();
20 let extra = jsonc_options.unwrap_or_default();
21
22 let parse_opts = JsoncParseOptions {
23 allow_comments: !extra.disallow_comments,
24 allow_trailing_commas: extra.allow_trailing_comma,
25 ..Default::default()
26 };
27
28 let value_opt = parse_to_serde_value(text, &parse_opts)?;
29 let value = value_opt.unwrap_or(JsonValue::Null);
30 Ok(Formatted::new(text, value, &fmt_opts))
31}
32
33pub fn stringify_jsonc(
35 formatted: &Formatted<JsonValue>,
36 options: Option<FormatOptions>,
37) -> serde_json::Result<String> {
38 crate::json::stringify_json_with_format(&formatted.value, &formatted.format, options)
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use crate::format::strip_line_comments;
46 use serde_json::Value as JsonValue;
47
48 const FIXTURE: &str = r#"
50{
51 // comment
52 "types": {
53 "boolean": true,
54 "integer": 1,
55 "float": 3.14,
56 "string": "hello",
57 "array": [
58 1,
59 2,
60 3
61 ],
62 "object": {
63 "key": "value"
64 },
65 "null": null,
66 "date": "1979-05-27T07:32:00-08:00"
67 }
68}
69"#;
70
71 #[test]
72 fn parse_ok() {
73 let formatted = parse_jsonc(FIXTURE, None, None).unwrap();
74 let types = &formatted.value["types"];
75 assert_eq!(types["boolean"], JsonValue::Bool(true));
76 assert_eq!(types["integer"], JsonValue::from(1));
77 assert!((types["float"].as_f64().unwrap() - 3.14).abs() < f64::EPSILON);
78 assert_eq!(types["string"], JsonValue::from("hello"));
79 assert_eq!(types["array"], JsonValue::from(vec![1, 2, 3]));
80 assert_eq!(types["object"]["key"], JsonValue::from("value"));
81 assert!(types["null"].is_null());
82 assert_eq!(types["date"], JsonValue::from("1979-05-27T07:32:00-08:00"));
83 }
84
85 #[test]
86 fn stringify_roundtrip() {
87 let formatted = parse_jsonc(FIXTURE, None, None).unwrap();
88 let out = stringify_jsonc(&formatted, None).unwrap();
89 let without_comments = strip_line_comments(FIXTURE, "//");
90 let expected_val: JsonValue = serde_json::from_str(&without_comments).unwrap();
91 let out_val: JsonValue = serde_json::from_str(&out).unwrap();
92 assert_eq!(out_val, expected_val);
93 }
94
95 #[test]
96 fn respects_disallow_comments_flag() {
97 let opts = JsoncExtraOptions {
98 disallow_comments: true,
99 allow_trailing_comma: false,
100 };
101
102 let result = parse_jsonc(FIXTURE, None, Some(opts));
103 assert!(
104 result.is_err(),
105 "expected error when comments are disallowed"
106 );
107 }
108
109 #[test]
110 fn trailing_commas_controlled_by_flag() {
111 const TRAILING_COMMA: &str = r#"
112{
113 "a": 1,
114}
115"#;
116
117 let res_default = parse_jsonc(TRAILING_COMMA, None, None);
119 assert!(res_default.is_err());
120
121 let opts = JsoncExtraOptions {
123 disallow_comments: false,
124 allow_trailing_comma: true,
125 };
126 let res_ok = parse_jsonc(TRAILING_COMMA, None, Some(opts));
127 assert!(res_ok.is_ok());
128 }
129}