pub const TRANSFORM_ID: &str = "json_minify";
pub const TRANSFORM_VERSION: &str = "1.0.0";
pub fn minify_json(input: &[u8]) -> Result<Vec<u8>, JsonMinifyError> {
if input.is_empty() {
return Ok(Vec::new());
}
serde_json::from_slice::<serde_json::Value>(input)?;
let mut out = Vec::with_capacity(input.len());
let mut in_string = false;
let mut escaped = false;
for &byte in input {
if in_string {
out.push(byte);
if escaped {
escaped = false;
} else if byte == b'\\' {
escaped = true;
} else if byte == b'"' {
in_string = false;
}
} else {
match byte {
b'"' => {
in_string = true;
out.push(byte);
}
b' ' | b'\n' | b'\r' | b'\t' => {}
_ => out.push(byte),
}
}
}
Ok(out)
}
#[derive(Debug, thiserror::Error)]
pub enum JsonMinifyError {
#[error("invalid json: {0}")]
Invalid(#[from] serde_json::Error),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn minified_output_is_still_valid_json() {
let input = br#"{ "a" : [1, 2, 3], "b" : { "c" : null } }"#;
let out = minify_json(input).unwrap();
let original: serde_json::Value = serde_json::from_slice(input).unwrap();
let minified: serde_json::Value = serde_json::from_slice(&out).unwrap();
assert_eq!(original, minified);
}
#[test]
fn key_order_is_preserved_byte_for_byte() {
let input = b"{\"b\": 1, \"a\": 2}";
let out = minify_json(input).unwrap();
assert_eq!(out, b"{\"b\":1,\"a\":2}");
}
#[test]
fn duplicate_keys_are_not_deduplicated() {
let input = b"{\"a\": 1, \"a\": 2}";
let out = minify_json(input).unwrap();
assert_eq!(out, b"{\"a\":1,\"a\":2}");
}
#[test]
fn string_content_and_escapes_survive_byte_for_byte() {
let input: &[u8] = br#"{"s": "line1\nline2 \"quoted\" back\\slash unicode\u00e9"}"#;
let out = minify_json(input).unwrap();
assert_eq!(
out,
br#"{"s":"line1\nline2 \"quoted\" back\\slash unicode\u00e9"}"#.to_vec()
);
}
#[test]
fn number_spelling_is_never_renormalized() {
let input = b"{ \"x\" : 1.0 , \"y\" : 1e10 }";
let out = minify_json(input).unwrap();
let out_str = std::str::from_utf8(&out).unwrap();
assert_eq!(out_str, "{\"x\":1.0,\"y\":1e10}");
assert!(out_str.contains("1.0"));
assert!(out_str.contains("1e10"));
assert!(!out_str.contains("10000000000"));
}
#[test]
fn non_utf8_input_is_rejected() {
let input: &[u8] = &[0xFF, 0xFE, 0x00];
let err = minify_json(input).unwrap_err();
assert!(matches!(err, JsonMinifyError::Invalid(_)));
}
#[test]
fn empty_input_returns_empty_output_not_an_error() {
let out = minify_json(b"").unwrap();
assert!(out.is_empty());
}
#[test]
fn unterminated_string_is_rejected() {
let input = b"{\"a\": \"b";
let err = minify_json(input).unwrap_err();
assert!(matches!(err, JsonMinifyError::Invalid(_)));
}
#[test]
fn minify_is_idempotent_on_whitespace_heavy_nested_input() {
let input =
b"{\n \"outer\" : {\n \"list\" : [ 1, 2,\t3 ],\n \"s\" : \"a\\tb\"\n }\n}\n";
let once = minify_json(input).unwrap();
let twice = minify_json(&once).unwrap();
assert_eq!(once, twice);
}
}