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