use serde::{Serialize, de::DeserializeOwned};
use crate::format::{FormatOptions, Formatted, compute_indent, wrap_whitespace};
pub fn parse_json<T>(text: &str, options: Option<FormatOptions>) -> serde_json::Result<Formatted<T>>
where
T: DeserializeOwned,
{
let opts = options.unwrap_or_default();
let value = serde_json::from_str(text)?;
Ok(Formatted::new(text, value, &opts))
}
pub fn stringify_json<T>(
formatted: &Formatted<T>,
options: Option<FormatOptions>,
) -> serde_json::Result<String>
where
T: Serialize,
{
stringify_json_with_format(&formatted.value, &formatted.format, options)
}
pub(crate) fn stringify_json_with_format<T>(
value: &T,
format: &crate::format::FormatInfo,
options: Option<FormatOptions>,
) -> serde_json::Result<String>
where
T: Serialize,
{
let opts = options.unwrap_or_default();
let indent = compute_indent(format, &opts);
let json = serde_json::to_string_pretty(value)?;
let indent_str = " ".repeat(indent);
let indented = json
.lines()
.map(|line| {
if line.is_empty() {
line.to_string()
} else {
let trimmed = line.trim_start();
let mut s = String::with_capacity(indent_str.len() + trimmed.len());
s.push_str(&indent_str);
s.push_str(trimmed);
s
}
})
.collect::<Vec<_>>()
.join("\n");
Ok(wrap_whitespace(&indented, format))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FormatInfo, Formatted};
use serde_json::Value as JsonValue;
const FIXTURE: &str = r#"
{
"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() {
#[derive(Debug, serde::Deserialize)]
struct Types {
boolean: bool,
integer: i64,
float: f64,
string: String,
array: Vec<i64>,
object: serde_json::Value,
null: Option<serde_json::Value>,
date: String,
}
#[derive(Debug, serde::Deserialize)]
struct Root {
types: Types,
}
let formatted = parse_json::<Root>(FIXTURE, None).unwrap();
assert!(formatted.value.types.boolean);
assert_eq!(formatted.value.types.integer, 1);
assert!((formatted.value.types.float - 3.14).abs() < f64::EPSILON);
assert_eq!(formatted.value.types.string, "hello");
assert_eq!(formatted.value.types.array, vec![1, 2, 3]);
assert_eq!(formatted.value.types.object["key"].as_str(), Some("value"));
assert!(formatted.value.types.null.is_none());
assert_eq!(
formatted.value.types.date,
"1979-05-27T07:32:00-08:00".to_string()
);
}
#[test]
fn stringify_roundtrip() {
let formatted = parse_json::<JsonValue>(FIXTURE, None).unwrap();
let out = stringify_json(&formatted, None).unwrap();
let out_val: JsonValue = serde_json::from_str(&out).unwrap();
let expected_val: JsonValue = serde_json::from_str(FIXTURE).unwrap();
assert_eq!(out_val, expected_val);
}
#[test]
fn stringify_from_raw_preserves_structure() {
let value: JsonValue = serde_json::from_str(FIXTURE).unwrap();
let formatted = Formatted {
value,
format: FormatInfo {
sample: None,
whitespace_start: String::new(),
whitespace_end: String::new(),
},
};
let out = stringify_json(&formatted, None).unwrap();
let out_val: JsonValue = serde_json::from_str(&out).unwrap();
let expected_val: JsonValue = serde_json::from_str(FIXTURE).unwrap();
assert_eq!(out_val, expected_val);
}
#[test]
fn stringify_respects_explicit_indent() {
let formatted = parse_json::<JsonValue>(FIXTURE, None).unwrap();
let mut opts = FormatOptions::default();
opts.indent = Some(4);
let out = stringify_json(&formatted, Some(opts)).unwrap();
let mut lines = out.lines();
assert_eq!(lines.next(), Some(""));
if let Some(second) = lines.next() {
let prefix = &second[..4.min(second.len())];
assert_eq!(prefix, " ");
} else {
panic!("expected at least two lines in JSON output");
}
}
#[test]
fn preserves_outer_whitespace() {
let text = " \n{ \"a\": 1 }\n\t";
let formatted = parse_json::<JsonValue>(text, None).unwrap();
let out = stringify_json(&formatted, None).unwrap();
assert!(out.starts_with(" \n"));
assert!(out.ends_with("\n\t"));
}
}