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