Skip to main content

c12_parser/
json5.rs

1use json5 as json5_crate;
2use serde::{Serialize, de::DeserializeOwned};
3
4use crate::format::{FormatOptions, Formatted, wrap_whitespace};
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    // json5 crate does not currently expose a configurable pretty printer
28    // in the same way as the JS version. We fall back to its default
29    // serialization behavior and only preserve outer whitespace.
30    let json5 = json5_crate::to_string(&formatted.value)?;
31    Ok(wrap_whitespace(&json5, &formatted.format))
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37    use serde_json::Value as JsonValue;
38
39    /// Sample document used by parse/stringify tests.
40    const FIXTURE: &str = r#"
41{
42  types: {
43    boolean: true,
44    integer: 1,
45    float: 3.14,
46    string: 'hello',
47    array: [
48      1,
49      2,
50      3,
51    ],
52    object: {
53      key: 'value',
54    },
55    null: null,
56    date: '1979-05-27T07:32:00-08:00',
57  },
58}
59"#;
60
61    #[test]
62    fn parse_ok() {
63        #[derive(Debug, serde::Deserialize)]
64        struct Types {
65            boolean: bool,
66            integer: i64,
67            float: f64,
68            string: String,
69            array: Vec<i64>,
70            object: serde_json::Value,
71            null: Option<serde_json::Value>,
72            date: String,
73        }
74
75        #[derive(Debug, serde::Deserialize)]
76        struct Root {
77            types: Types,
78        }
79
80        let formatted = parse_json5::<Root>(FIXTURE, None).unwrap();
81        assert!(formatted.value.types.boolean);
82        assert_eq!(formatted.value.types.integer, 1);
83        assert!((formatted.value.types.float - 3.14).abs() < f64::EPSILON);
84        assert_eq!(formatted.value.types.string, "hello");
85        assert_eq!(formatted.value.types.array, vec![1, 2, 3]);
86        assert_eq!(formatted.value.types.object["key"].as_str(), Some("value"));
87        assert!(formatted.value.types.null.is_none());
88        assert_eq!(
89            formatted.value.types.date,
90            "1979-05-27T07:32:00-08:00".to_string()
91        );
92    }
93
94    #[test]
95    fn stringify_roundtrip() {
96        let formatted = parse_json5::<JsonValue>(FIXTURE, None).unwrap();
97        let out = stringify_json5(&formatted, None).unwrap();
98        let expected: JsonValue = ::json5::from_str(FIXTURE).unwrap();
99        let expected_str = ::json5::to_string(&expected).unwrap();
100        let expected_str = format!("\n{}", expected_str);
101        assert_eq!(out.trim(), expected_str.trim());
102    }
103
104    #[test]
105    fn preserves_outer_whitespace() {
106        let text = " \n{ types: { boolean: true } }\n\t";
107        let formatted = parse_json5::<JsonValue>(text, None).unwrap();
108        let out = stringify_json5(&formatted, None).unwrap();
109
110        assert!(out.starts_with(" \n"));
111        assert!(out.ends_with("\n\t"));
112    }
113}