Skip to main content

c12_parser/
jsonc.rs

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/// Extra options for JSONC parsing.
8#[derive(Clone, Debug, Default)]
9pub struct JsoncExtraOptions {
10    pub disallow_comments: bool,
11    pub allow_trailing_comma: bool,
12}
13
14/// Parses a JSONC string into a serde_json::Value, capturing formatting.
15pub 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
34/// Stringifies a JSONC value (as plain JSON) with preserved formatting.
35pub fn stringify_jsonc(
36    formatted: &Formatted<JsonValue>,
37    options: Option<FormatOptions>,
38) -> serde_json::Result<String> {
39    // JSONC comments/trailing commas are not preserved; we emit plain JSON.
40    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        // 默认不允许尾逗号,应当报错。
137        let res_default = parse_jsonc(TRAILING_COMMA, None, None);
138        assert!(res_default.is_err());
139
140        // 显式允许尾逗号,应当解析成功。
141        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}