Rust example: Stream-decompress XZ-compressed JSONlines file and print a single attribute

This example program parses a XZ-compressed JSONlines file and prints the timestamp attribute of each line if present. Invalid or empty lines are ignored.

main.rs

//! This program streams and decompresses a JSONlines file compressed with XZ,
//! parses each line as JSON, and prints the "timestamp" field if present.
//! Invalid or empty lines are ignored.

use std::fs::File; // For opening the file
use std::io::{BufRead, BufReader}; // For buffered reading
use anyhow::{Context, Result}; // For error handling
use serde::Deserialize; // For deserializing JSON
use xz2::read::XzDecoder; // For XZ decompression

/// Represents a single line in the JSONlines file.
/// Only the "timestamp" field is extracted.
#[derive(Deserialize, Debug)]
struct Line {
    timestamp: Option<f64>,
}

/// Processes the given XZ-compressed JSONlines file.
/// Decompresses it on the fly, parses each line as JSON,
/// and prints the timestamp if available. Ignores invalid lines.
fn process_file(path: &str) -> Result<()> {
    // Open the file
    let file = File::open(path).with_context(|| format!("Failed to open file: {}", path))?;
    // Create a buffered reader for efficiency
    let reader = BufReader::new(file);
    // Wrap with XZ decompressor
    let decompressor = XzDecoder::new(reader);
    // Another buffered reader for line-by-line reading
    let buf_reader = BufReader::new(decompressor);

    // Iterate over each line
    for line_result in buf_reader.lines() {
        let line = match line_result {
            Ok(l) => l,
            Err(_) => continue, // Skip lines with IO errors
        };
        // Skip empty lines
        if line.trim().is_empty() {
            continue;
        }
        // Try to parse as JSON
        match serde_json::from_str::<Line>(&line) {
            Ok(obj) => {
                // Print timestamp if present
                if let Some(ts) = obj.timestamp {
                    println!("{}", ts);
                }
            }
            Err(e) => {
                eprintln!("Failed to parse JSON line: {}", e);
                continue;
            }
        }
    }
    Ok(())
}

/// Main entry point.
/// Takes the file path as the first argument, or defaults to the example file.
fn main() -> Result<()> {
    // Require the file path as the first positional argument.
    // If missing, print a short usage message and exit with a non-zero code.
    let mut args = std::env::args();
    let prog = args.next().unwrap_or_else(|| "rust_parse_status_logs".to_string());
    let path = match args.next() {
        Some(p) => p,
        None => {
            eprintln!("Usage: {} <path-to-xz-jsonlines-file>", prog);
            std::process::exit(2);
        }
    };

    // Process the file
    process_file(&path)
}

Cargo.toml

[package]
name = "rust_parse_status_logs"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
xz2 = "0.1"
anyhow = "1"

Example input

{"timestamp": 1744267290.991, "status": "ok"}
{"timestamp": 1744267291.987, "status": "ok"}
{"status": "missing timestamp"}
invalid json line

Note that you need to compress this file with XZ to create the input file for the program.

xz example.json

Building & running

cargo run -- example.json.xz

Expected output:

    Finished dev [unoptimized + debuginfo] target(s) in 0.05s
     Running `target/debug/rust_parse_status_logs example.json.xz`
1744267290.991
1744267291.987
Failed to parse JSON line: expected value at line 1 column 1