Skip to main content

c12_parser/
json5.rs

1use json5 as json5_crate;
2use serde::{Serialize, de::DeserializeOwned};
3
4use crate::format::{FormatOptions, Formatted, compute_indent};
5
6/// Parses a JSON5 string into a value, capturing its formatting.
7pub fn parse_json5<T>(
8    text: &str,
9    options: Option<FormatOptions>,
10) -> Result<Formatted<T>, json5_crate::Error>
11where
12    T: DeserializeOwned,
13{
14    let opts = options.unwrap_or_default();
15    let value = json5_crate::from_str(text)?;
16    Ok(Formatted::new(text, value, &opts))
17}
18
19/// Stringifies a JSON5 value with preserved or configured formatting.
20pub fn stringify_json5<T>(
21    formatted: &Formatted<T>,
22    options: Option<FormatOptions>,
23) -> Result<String, json5_crate::Error>
24where
25    T: Serialize,
26{
27    let opts = options.unwrap_or_default();
28    let _indent = compute_indent(&formatted.format, &opts);
29    // json5 crate does not currently expose a configurable pretty printer
30    // in the same way as the JS version. We fall back to its default
31    // serialization behavior and only preserve outer whitespace.
32    let json5 = json5_crate::to_string(&formatted.value)?;
33    Ok(format!(
34        "{}{}{}",
35        formatted.format.whitespace_start, json5, formatted.format.whitespace_end
36    ))
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use serde_json::Value as JsonValue;
43
44    const JSON5_FIXTURE: &str = r#"
45{
46  types: {
47    boolean: true,
48    integer: 1,
49    float: 3.14,
50    string: 'hello',
51    array: [
52      1,
53      2,
54      3,
55    ],
56    object: {
57      key: 'value',
58    },
59    null: null,
60    date: '1979-05-27T07:32:00-08:00',
61  },
62}
63"#;
64
65    #[test]
66    fn json5_parse_matches_structure() {
67        #[derive(Debug, serde::Deserialize)]
68        struct Types {
69            boolean: bool,
70            integer: i64,
71            float: f64,
72            string: String,
73            array: Vec<i64>,
74            object: serde_json::Value,
75            null: Option<serde_json::Value>,
76            date: String,
77        }
78
79        #[derive(Debug, serde::Deserialize)]
80        struct Root {
81            types: Types,
82        }
83
84        let formatted = parse_json5::<Root>(JSON5_FIXTURE, None).unwrap();
85        assert!(formatted.value.types.boolean);
86        assert_eq!(formatted.value.types.integer, 1);
87        assert!((formatted.value.types.float - 3.14).abs() < f64::EPSILON);
88        assert_eq!(formatted.value.types.string, "hello");
89        assert_eq!(formatted.value.types.array, vec![1, 2, 3]);
90        assert_eq!(formatted.value.types.object["key"].as_str(), Some("value"));
91        assert!(formatted.value.types.null.is_none());
92        assert_eq!(
93            formatted.value.types.date,
94            "1979-05-27T07:32:00-08:00".to_string()
95        );
96    }
97
98    #[test]
99    fn json5_stringify_exact_normalized() {
100        let formatted = parse_json5::<JsonValue>(JSON5_FIXTURE, None).unwrap();
101        let out = stringify_json5(&formatted, None).unwrap();
102        let expected: JsonValue = ::json5::from_str(JSON5_FIXTURE).unwrap();
103        let expected_str = ::json5::to_string(&expected).unwrap();
104        let expected_str = format!("\n{}", expected_str);
105        assert_eq!(out.trim(), expected_str.trim());
106    }
107
108    #[test]
109    fn json5_preserves_outer_whitespace() {
110        let text = " \n{ types: { boolean: true } }\n\t";
111        let formatted = parse_json5::<JsonValue>(text, None).unwrap();
112        let out = stringify_json5(&formatted, None).unwrap();
113
114        assert!(out.starts_with(" \n"));
115        assert!(out.ends_with("\n\t"));
116    }
117}