Skip to main content

c12_parser/
json.rs

1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::format::{FormatOptions, Formatted, compute_indent, wrap_whitespace};
4
5/// Parses a JSON string into a value, capturing its formatting.
6pub fn parse_json<T>(text: &str, options: Option<FormatOptions>) -> serde_json::Result<Formatted<T>>
7where
8    T: DeserializeOwned,
9{
10    let opts = options.unwrap_or_default();
11    let value = serde_json::from_str(text)?;
12    Ok(Formatted::new(text, value, &opts))
13}
14
15/// Stringifies a JSON value with preserved or configured formatting.
16pub fn stringify_json<T>(
17    formatted: &Formatted<T>,
18    options: Option<FormatOptions>,
19) -> serde_json::Result<String>
20where
21    T: Serialize,
22{
23    stringify_json_with_format(&formatted.value, &formatted.format, options)
24}
25
26/// Stringifies a JSON value with explicit format info (avoids cloning when caller has value + format).
27pub(crate) fn stringify_json_with_format<T>(
28    value: &T,
29    format: &crate::format::FormatInfo,
30    options: Option<FormatOptions>,
31) -> serde_json::Result<String>
32where
33    T: Serialize,
34{
35    let opts = options.unwrap_or_default();
36    let indent = compute_indent(format, &opts);
37    let json = serde_json::to_string_pretty(value)?;
38    let indent_str = " ".repeat(indent);
39
40    let indented = json
41        .lines()
42        .map(|line| {
43            if line.is_empty() {
44                line.to_string()
45            } else {
46                let trimmed = line.trim_start();
47                let mut s = String::with_capacity(indent_str.len() + trimmed.len());
48                s.push_str(&indent_str);
49                s.push_str(trimmed);
50                s
51            }
52        })
53        .collect::<Vec<_>>()
54        .join("\n");
55    Ok(wrap_whitespace(&indented, format))
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use crate::{FormatInfo, Formatted};
62    use serde_json::Value as JsonValue;
63
64    /// Sample document used by parse/stringify tests.
65    const FIXTURE: &str = r#"
66{
67  "types": {
68    "boolean": true,
69    "integer": 1,
70    "float": 3.14,
71    "string": "hello",
72    "array": [
73      1,
74      2,
75      3
76    ],
77    "object": {
78      "key": "value"
79    },
80    "null": null,
81    "date": "1979-05-27T07:32:00-08:00"
82  }
83}
84"#;
85
86    #[test]
87    fn parse_ok() {
88        #[derive(Debug, serde::Deserialize)]
89        struct Types {
90            boolean: bool,
91            integer: i64,
92            float: f64,
93            string: String,
94            array: Vec<i64>,
95            object: serde_json::Value,
96            null: Option<serde_json::Value>,
97            date: String,
98        }
99
100        #[derive(Debug, serde::Deserialize)]
101        struct Root {
102            types: Types,
103        }
104
105        let formatted = parse_json::<Root>(FIXTURE, None).unwrap();
106        assert!(formatted.value.types.boolean);
107        assert_eq!(formatted.value.types.integer, 1);
108        assert!((formatted.value.types.float - 3.14).abs() < f64::EPSILON);
109        assert_eq!(formatted.value.types.string, "hello");
110        assert_eq!(formatted.value.types.array, vec![1, 2, 3]);
111        assert_eq!(formatted.value.types.object["key"].as_str(), Some("value"));
112        assert!(formatted.value.types.null.is_none());
113        assert_eq!(
114            formatted.value.types.date,
115            "1979-05-27T07:32:00-08:00".to_string()
116        );
117    }
118
119    #[test]
120    fn stringify_roundtrip() {
121        let formatted = parse_json::<JsonValue>(FIXTURE, None).unwrap();
122        let out = stringify_json(&formatted, None).unwrap();
123        let out_val: JsonValue = serde_json::from_str(&out).unwrap();
124        let expected_val: JsonValue = serde_json::from_str(FIXTURE).unwrap();
125        assert_eq!(out_val, expected_val);
126    }
127
128    #[test]
129    fn stringify_from_raw_preserves_structure() {
130        let value: JsonValue = serde_json::from_str(FIXTURE).unwrap();
131        let formatted = Formatted {
132            value,
133            format: FormatInfo {
134                sample: None,
135                whitespace_start: String::new(),
136                whitespace_end: String::new(),
137            },
138        };
139        let out = stringify_json(&formatted, None).unwrap();
140        let out_val: JsonValue = serde_json::from_str(&out).unwrap();
141        let expected_val: JsonValue = serde_json::from_str(FIXTURE).unwrap();
142        assert_eq!(out_val, expected_val);
143    }
144
145    #[test]
146    fn stringify_respects_explicit_indent() {
147        let formatted = parse_json::<JsonValue>(FIXTURE, None).unwrap();
148        let mut opts = FormatOptions::default();
149        opts.indent = Some(4);
150
151        let out = stringify_json(&formatted, Some(opts)).unwrap();
152
153        // 第一行是空行(前导换行),第二行应为带 4 个空格缩进的 "{".
154        let mut lines = out.lines();
155        assert_eq!(lines.next(), Some(""));
156        if let Some(second) = lines.next() {
157            let prefix = &second[..4.min(second.len())];
158            assert_eq!(prefix, "    ");
159        } else {
160            panic!("expected at least two lines in JSON output");
161        }
162    }
163
164    #[test]
165    fn preserves_outer_whitespace() {
166        let text = " \n{ \"a\": 1 }\n\t";
167        let formatted = parse_json::<JsonValue>(text, None).unwrap();
168        let out = stringify_json(&formatted, None).unwrap();
169
170        assert!(out.starts_with(" \n"));
171        assert!(out.ends_with("\n\t"));
172    }
173}