1use serde::{Serialize, de::DeserializeOwned};
2
3use crate::format::{FormatOptions, Formatted, compute_indent};
4
5pub 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
15pub fn stringify_json<T>(
17 formatted: &Formatted<T>,
18 options: Option<FormatOptions>,
19) -> serde_json::Result<String>
20where
21 T: Serialize,
22{
23 let opts = options.unwrap_or_default();
24 let indent = compute_indent(&formatted.format, &opts);
25 let json = serde_json::to_string_pretty(&formatted.value)?;
26 let indent_str = " ".repeat(indent);
27
28 let indented = json
29 .lines()
30 .map(|line| {
31 if line.is_empty() {
32 line.to_string()
33 } else {
34 let trimmed = line.trim_start();
35 let mut s = String::with_capacity(indent_str.len() + trimmed.len());
36 s.push_str(&indent_str);
37 s.push_str(trimmed);
38 s
39 }
40 })
41 .collect::<Vec<_>>()
42 .join("\n");
43 Ok(format!(
44 "{}{}{}",
45 formatted.format.whitespace_start, indented, formatted.format.whitespace_end
46 ))
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52 use crate::format::{FormatInfo, Formatted};
53 use serde_json::Value as JsonValue;
54
55 const JSON_FIXTURE: &str = r#"
56{
57 "types": {
58 "boolean": true,
59 "integer": 1,
60 "float": 3.14,
61 "string": "hello",
62 "array": [
63 1,
64 2,
65 3
66 ],
67 "object": {
68 "key": "value"
69 },
70 "null": null,
71 "date": "1979-05-27T07:32:00-08:00"
72 }
73}
74"#;
75
76 #[test]
77 fn json_parse_ok() {
78 #[derive(Debug, serde::Deserialize)]
79 struct Types {
80 boolean: bool,
81 integer: i64,
82 float: f64,
83 string: String,
84 array: Vec<i64>,
85 object: serde_json::Value,
86 null: Option<serde_json::Value>,
87 date: String,
88 }
89
90 #[derive(Debug, serde::Deserialize)]
91 struct Root {
92 types: Types,
93 }
94
95 let formatted = parse_json::<Root>(JSON_FIXTURE, None).unwrap();
96 assert!(formatted.value.types.boolean);
97 assert_eq!(formatted.value.types.integer, 1);
98 assert!((formatted.value.types.float - 3.14).abs() < f64::EPSILON);
99 assert_eq!(formatted.value.types.string, "hello");
100 assert_eq!(formatted.value.types.array, vec![1, 2, 3]);
101 assert_eq!(formatted.value.types.object["key"].as_str(), Some("value"));
102 assert!(formatted.value.types.null.is_none());
103 assert_eq!(
104 formatted.value.types.date,
105 "1979-05-27T07:32:00-08:00".to_string()
106 );
107 }
108
109 #[test]
110 fn json_stringify_exact_fixture() {
111 let formatted = parse_json::<JsonValue>(JSON_FIXTURE, None).unwrap();
112 let out = stringify_json(&formatted, None).unwrap();
113 let out_val: JsonValue = serde_json::from_str(&out).unwrap();
114 let expected_val: JsonValue = serde_json::from_str(JSON_FIXTURE).unwrap();
115 assert_eq!(out_val, expected_val);
116 }
117
118 #[test]
119 fn json_stringify_from_raw_object_matches_trimmed_fixture() {
120 let value: JsonValue = serde_json::from_str(JSON_FIXTURE).unwrap();
121 let formatted = Formatted {
122 value,
123 format: FormatInfo {
124 sample: None,
125 whitespace_start: String::new(),
126 whitespace_end: String::new(),
127 },
128 };
129 let out = stringify_json(&formatted, None).unwrap();
130 let out_val: JsonValue = serde_json::from_str(&out).unwrap();
131 let expected_val: JsonValue = serde_json::from_str(JSON_FIXTURE).unwrap();
132 assert_eq!(out_val, expected_val);
133 }
134
135 #[test]
136 fn json_stringify_respects_explicit_indent() {
137 let formatted = parse_json::<JsonValue>(JSON_FIXTURE, None).unwrap();
138 let mut opts = FormatOptions::default();
139 opts.indent = Some(4);
140
141 let out = stringify_json(&formatted, Some(opts)).unwrap();
142
143 let mut lines = out.lines();
145 assert_eq!(lines.next(), Some(""));
146 if let Some(second) = lines.next() {
147 let prefix = &second[..4.min(second.len())];
148 assert_eq!(prefix, " ");
149 } else {
150 panic!("expected at least two lines in JSON output");
151 }
152 }
153
154 #[test]
155 fn json_preserves_outer_whitespace() {
156 let text = " \n{ \"a\": 1 }\n\t";
157 let formatted = parse_json::<JsonValue>(text, None).unwrap();
158 let out = stringify_json(&formatted, None).unwrap();
159
160 assert!(out.starts_with(" \n"));
161 assert!(out.ends_with("\n\t"));
162 }
163}