use serde::{Deserialize, Serialize};
use spring_batch_rs::{
BatchError,
core::{
item::{ItemProcessor, PassThroughProcessor},
job::{Job, JobBuilder},
step::StepBuilder,
},
item::{
csv::csv_writer::CsvItemWriterBuilder, json::json_reader::JsonItemReaderBuilder,
json::json_writer::JsonItemWriterBuilder, logger::LoggerWriterBuilder,
},
};
use std::env::temp_dir;
use std::io::Cursor;
#[derive(Debug, Clone, Deserialize, Serialize)]
struct Order {
id: u64,
customer: String,
total: f64,
status: String,
}
#[derive(Debug, Clone, Serialize)]
struct OrderSummary {
order_id: u64,
customer_name: String,
amount: f64,
}
struct OrderSummaryProcessor;
impl ItemProcessor<Order, OrderSummary> for OrderSummaryProcessor {
fn process(&self, item: &Order) -> Result<Option<OrderSummary>, BatchError> {
Ok(Some(OrderSummary {
order_id: item.id,
customer_name: item.customer.clone(),
amount: item.total,
}))
}
}
struct CompletedOrderProcessor {
tax_rate: f64,
}
impl CompletedOrderProcessor {
fn new(tax_rate: f64) -> Self {
Self { tax_rate }
}
}
impl ItemProcessor<Order, Order> for CompletedOrderProcessor {
fn process(&self, item: &Order) -> Result<Option<Order>, BatchError> {
let total = if item.status == "completed" {
item.total * (1.0 + self.tax_rate)
} else {
item.total
};
Ok(Some(Order {
id: item.id,
customer: item.customer.clone(),
total,
status: item.status.clone(),
}))
}
}
fn example_read_json_array() -> Result<(), BatchError> {
println!("=== Example 1: Read JSON Array ===");
let json_data = r#"[
{"id": 1, "customer": "Alice", "total": 99.99, "status": "completed"},
{"id": 2, "customer": "Bob", "total": 149.50, "status": "pending"},
{"id": 3, "customer": "Charlie", "total": 75.00, "status": "completed"}
]"#;
let reader = JsonItemReaderBuilder::<Order>::new().from_reader(Cursor::new(json_data));
let writer = LoggerWriterBuilder::<Order>::new().build();
let processor = PassThroughProcessor::<Order>::new();
let step = StepBuilder::new("read-json-array")
.chunk::<Order, Order>(2)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let job = JobBuilder::new().start(&step).build();
let result = job.run()?;
let step_exec = job.get_step_execution("read-json-array").unwrap();
println!(" Items read: {}", step_exec.read_count);
println!(" Duration: {:?}", result.duration);
Ok(())
}
fn example_json_transformation() -> Result<(), BatchError> {
println!("\n=== Example 2: JSON Transformation ===");
let json_data = r#"[
{"id": 1, "customer": "Alice", "total": 100.00, "status": "completed"},
{"id": 2, "customer": "Bob", "total": 200.00, "status": "pending"},
{"id": 3, "customer": "Charlie", "total": 150.00, "status": "completed"}
]"#;
let reader = JsonItemReaderBuilder::<Order>::new().from_reader(Cursor::new(json_data));
let output_path = temp_dir().join("orders_with_tax.json");
let writer = JsonItemWriterBuilder::<Order>::new()
.pretty_formatter(true)
.from_path(&output_path);
let processor = CompletedOrderProcessor::new(0.08);
let step = StepBuilder::new("apply-tax")
.chunk::<Order, Order>(10)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let job = JobBuilder::new().start(&step).build();
job.run()?;
println!(" Applied 8% tax to completed orders");
println!(" Output: {}", output_path.display());
Ok(())
}
fn example_json_to_csv() -> Result<(), BatchError> {
println!("\n=== Example 3: JSON to CSV Export ===");
let json_data = r#"[
{"id": 1001, "customer": "Alice Johnson", "total": 299.99, "status": "completed"},
{"id": 1002, "customer": "Bob Smith", "total": 149.50, "status": "completed"},
{"id": 1003, "customer": "Charlie Brown", "total": 75.00, "status": "pending"}
]"#;
let reader = JsonItemReaderBuilder::<Order>::new().from_reader(Cursor::new(json_data));
let output_path = temp_dir().join("order_summary.csv");
let writer = CsvItemWriterBuilder::<OrderSummary>::new()
.has_headers(true)
.from_path(&output_path);
let processor = OrderSummaryProcessor;
let step = StepBuilder::new("json-to-csv")
.chunk::<Order, OrderSummary>(10)
.reader(&reader)
.processor(&processor)
.writer(&writer)
.build();
let job = JobBuilder::new().start(&step).build();
job.run()?;
println!(" Exported orders to CSV summary");
println!(" Output: {}", output_path.display());
Ok(())
}
fn example_json_formatting() -> Result<(), BatchError> {
println!("\n=== Example 4: JSON Formatting Options ===");
let json_data = r#"[
{"id": 1, "customer": "Alice", "total": 99.99, "status": "completed"}
]"#;
let reader1 = JsonItemReaderBuilder::<Order>::new().from_reader(Cursor::new(json_data));
let compact_path = temp_dir().join("orders_compact.json");
let writer1 = JsonItemWriterBuilder::<Order>::new()
.pretty_formatter(false)
.from_path(&compact_path);
let processor1 = PassThroughProcessor::<Order>::new();
let step1 = StepBuilder::new("compact-json")
.chunk::<Order, Order>(10)
.reader(&reader1)
.processor(&processor1)
.writer(&writer1)
.build();
let reader2 = JsonItemReaderBuilder::<Order>::new().from_reader(Cursor::new(json_data));
let pretty_path = temp_dir().join("orders_pretty.json");
let writer2 = JsonItemWriterBuilder::<Order>::new()
.pretty_formatter(true)
.from_path(&pretty_path);
let processor2 = PassThroughProcessor::<Order>::new();
let step2 = StepBuilder::new("pretty-json")
.chunk::<Order, Order>(10)
.reader(&reader2)
.processor(&processor2)
.writer(&writer2)
.build();
let job = JobBuilder::new().start(&step1).next(&step2).build();
job.run()?;
println!(" Compact output: {}", compact_path.display());
println!(" Pretty output: {}", pretty_path.display());
Ok(())
}
fn main() -> Result<(), BatchError> {
env_logger::init();
println!("JSON Processing Examples");
println!("========================\n");
example_read_json_array()?;
example_json_transformation()?;
example_json_to_csv()?;
example_json_formatting()?;
println!("\n✓ All JSON examples completed successfully!");
Ok(())
}