use jsonc_parser::{ParseOptions as JsoncParseOptions, parse_to_serde_value};
use serde_json::Value as JsonValue;
use crate::format::{FormatOptions, Formatted};
#[derive(Clone, Debug, Default)]
pub struct JsoncExtraOptions {
pub disallow_comments: bool,
pub allow_trailing_comma: bool,
}
pub fn parse_jsonc(
text: &str,
fmt_options: Option<FormatOptions>,
jsonc_options: Option<JsoncExtraOptions>,
) -> Result<Formatted<JsonValue>, Box<dyn std::error::Error>> {
let fmt_opts = fmt_options.unwrap_or_default();
let extra = jsonc_options.unwrap_or_default();
let parse_opts = JsoncParseOptions {
allow_comments: !extra.disallow_comments,
allow_trailing_commas: extra.allow_trailing_comma,
..Default::default()
};
let value_opt = parse_to_serde_value(text, &parse_opts)?;
let value = value_opt.unwrap_or(JsonValue::Null);
Ok(Formatted::new(text, value, &fmt_opts))
}
pub fn stringify_jsonc(
formatted: &Formatted<JsonValue>,
options: Option<FormatOptions>,
) -> serde_json::Result<String> {
crate::json::stringify_json_with_format(&formatted.value, &formatted.format, options)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::strip_line_comments;
use serde_json::Value as JsonValue;
const FIXTURE: &str = r#"
{
// comment
"types": {
"boolean": true,
"integer": 1,
"float": 3.14,
"string": "hello",
"array": [
1,
2,
3
],
"object": {
"key": "value"
},
"null": null,
"date": "1979-05-27T07:32:00-08:00"
}
}
"#;
#[test]
fn parse_ok() {
let formatted = parse_jsonc(FIXTURE, None, None).unwrap();
let types = &formatted.value["types"];
assert_eq!(types["boolean"], JsonValue::Bool(true));
assert_eq!(types["integer"], JsonValue::from(1));
assert!((types["float"].as_f64().unwrap() - 3.14).abs() < f64::EPSILON);
assert_eq!(types["string"], JsonValue::from("hello"));
assert_eq!(types["array"], JsonValue::from(vec![1, 2, 3]));
assert_eq!(types["object"]["key"], JsonValue::from("value"));
assert!(types["null"].is_null());
assert_eq!(types["date"], JsonValue::from("1979-05-27T07:32:00-08:00"));
}
#[test]
fn stringify_roundtrip() {
let formatted = parse_jsonc(FIXTURE, None, None).unwrap();
let out = stringify_jsonc(&formatted, None).unwrap();
let without_comments = strip_line_comments(FIXTURE, "//");
let expected_val: JsonValue = serde_json::from_str(&without_comments).unwrap();
let out_val: JsonValue = serde_json::from_str(&out).unwrap();
assert_eq!(out_val, expected_val);
}
#[test]
fn respects_disallow_comments_flag() {
let opts = JsoncExtraOptions {
disallow_comments: true,
allow_trailing_comma: false,
};
let result = parse_jsonc(FIXTURE, None, Some(opts));
assert!(
result.is_err(),
"expected error when comments are disallowed"
);
}
#[test]
fn trailing_commas_controlled_by_flag() {
const TRAILING_COMMA: &str = r#"
{
"a": 1,
}
"#;
let res_default = parse_jsonc(TRAILING_COMMA, None, None);
assert!(res_default.is_err());
let opts = JsoncExtraOptions {
disallow_comments: false,
allow_trailing_comma: true,
};
let res_ok = parse_jsonc(TRAILING_COMMA, None, Some(opts));
assert!(res_ok.is_ok());
}
}