use crate::error::Result;
use serde::{Deserialize, Serialize};
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};
use std::path::Path;
const BUFFER_SIZE: usize = 64 * 1024;
pub fn write_json_optimized<T, P>(data: &T, path: P) -> Result<()>
where
T: Serialize,
P: AsRef<Path>,
{
let file = File::create(path)?;
let mut writer = BufWriter::with_capacity(BUFFER_SIZE, file);
serde_json::to_writer_pretty(&mut writer, data)?;
writer.flush()?;
Ok(())
}
pub fn write_json_compact_optimized<T, P>(data: &T, path: P) -> Result<()>
where
T: Serialize,
P: AsRef<Path>,
{
let file = File::create(path)?;
let mut writer = BufWriter::with_capacity(BUFFER_SIZE, file);
serde_json::to_writer(&mut writer, data)?;
writer.flush()?;
Ok(())
}
pub fn read_json_optimized<T, P>(path: P) -> Result<T>
where
T: for<'de> Deserialize<'de>,
P: AsRef<Path>,
{
let file = File::open(path)?;
let mut reader = BufReader::with_capacity(BUFFER_SIZE, file);
let data = serde_json::from_reader(&mut reader)?;
Ok(data)
}
pub fn read_json_string_optimized<P>(path: P) -> Result<String>
where
P: AsRef<Path>,
{
let file = File::open(path)?;
let mut reader = BufReader::with_capacity(BUFFER_SIZE, file);
let mut contents = String::new();
reader.read_to_string(&mut contents)?;
Ok(contents)
}
#[doc(hidden)]
pub fn write_json_naive<T, P>(data: &T, path: P) -> Result<()>
where
T: Serialize,
P: AsRef<Path>,
{
let json = serde_json::to_string_pretty(data)?;
std::fs::write(path, json)?;
Ok(())
}
#[doc(hidden)]
pub fn read_json_naive<T, P>(path: P) -> Result<T>
where
T: for<'de> Deserialize<'de>,
P: AsRef<Path>,
{
let json = std::fs::read_to_string(path)?;
let data = serde_json::from_str(&json)?;
Ok(data)
}
#[cfg(test)]
mod tests {
use super::*;
use serde::{Deserialize, Serialize};
use tempfile::NamedTempFile;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct TestData {
name: String,
value: i32,
items: Vec<String>,
}
fn create_test_data() -> TestData {
TestData {
name: "test".to_string(),
value: 42,
items: vec![
"item1".to_string(),
"item2".to_string(),
"item3".to_string(),
],
}
}
#[test]
fn test_write_json_optimized() {
let data = create_test_data();
let temp_file = NamedTempFile::new().unwrap();
write_json_optimized(&data, temp_file.path()).unwrap();
let content = std::fs::read_to_string(temp_file.path()).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(parsed["name"], "test");
assert_eq!(parsed["value"], 42);
assert_eq!(parsed["items"].as_array().unwrap().len(), 3);
}
#[test]
fn test_write_json_compact_optimized() {
let data = create_test_data();
let temp_file = NamedTempFile::new().unwrap();
write_json_compact_optimized(&data, temp_file.path()).unwrap();
let content = std::fs::read_to_string(temp_file.path()).unwrap();
assert!(!content.contains(" "));
let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(parsed["name"], "test");
}
#[test]
fn test_read_json_optimized() {
let data = create_test_data();
let temp_file = NamedTempFile::new().unwrap();
write_json_optimized(&data, temp_file.path()).unwrap();
let read_data: TestData = read_json_optimized(temp_file.path()).unwrap();
assert_eq!(read_data, data);
}
#[test]
fn test_read_json_string_optimized() {
let data = create_test_data();
let temp_file = NamedTempFile::new().unwrap();
write_json_optimized(&data, temp_file.path()).unwrap();
let json_string = read_json_string_optimized(temp_file.path()).unwrap();
assert!(json_string.contains("\"name\": \"test\""));
assert!(json_string.contains("\"value\": 42"));
let parsed: TestData = serde_json::from_str(&json_string).unwrap();
assert_eq!(parsed, data);
}
#[test]
fn test_roundtrip_optimized() {
let original = create_test_data();
let temp_file = NamedTempFile::new().unwrap();
write_json_optimized(&original, temp_file.path()).unwrap();
let roundtrip: TestData = read_json_optimized(temp_file.path()).unwrap();
assert_eq!(roundtrip, original);
}
#[test]
fn test_naive_vs_optimized_correctness() {
let data = create_test_data();
let temp_optimized = NamedTempFile::new().unwrap();
let temp_naive = NamedTempFile::new().unwrap();
write_json_optimized(&data, temp_optimized.path()).unwrap();
write_json_naive(&data, temp_naive.path()).unwrap();
let optimized: TestData = read_json_optimized(temp_optimized.path()).unwrap();
let naive: TestData = read_json_naive(temp_naive.path()).unwrap();
assert_eq!(optimized, naive);
assert_eq!(optimized, data);
}
#[test]
#[cfg_attr(
all(target_os = "linux", not(target_env = "musl")),
ignore = "Requires >20MB memory allocation, fails in CI environments"
)]
fn test_large_data_handling() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
struct LargeData {
items: Vec<TestData>,
}
let large_data = LargeData {
items: (0..1000)
.map(|i| TestData {
name: format!("item-{}", i),
value: i,
items: vec![format!("sub-{}", i); 10],
})
.collect(),
};
let temp_file = NamedTempFile::new().unwrap();
write_json_optimized(&large_data, temp_file.path()).unwrap();
let read_data: LargeData = read_json_optimized(temp_file.path()).unwrap();
assert_eq!(read_data.items.len(), 1000);
assert_eq!(read_data.items[0].name, "item-0");
assert_eq!(read_data.items[999].name, "item-999");
}
}