jarq 0.7.3

An interactive jq-like JSON query tool with a TUI
Documentation
use crossbeam_channel::{Receiver, unbounded};
use simd_json::OwnedValue as Value;
use std::fs;
use std::io::{self, Read};
use std::path::PathBuf;
use std::thread::{self, JoinHandle};

pub struct LoadResult {
    pub result: Result<Vec<Value>, String>,
}

pub struct Loader {
    result_rx: Receiver<LoadResult>,
    _handle: JoinHandle<()>,
}

impl Loader {
    pub fn spawn_file(path: PathBuf) -> Self {
        let (result_tx, result_rx) = unbounded();
        let handle = thread::spawn(move || {
            let result = load_file(&path);
            let _ = result_tx.send(LoadResult { result });
        });
        Loader {
            result_rx,
            _handle: handle,
        }
    }

    pub fn spawn_stdin() -> Self {
        let (result_tx, result_rx) = unbounded();
        let handle = thread::spawn(move || {
            let result = load_stdin();
            let _ = result_tx.send(LoadResult { result });
        });
        Loader {
            result_rx,
            _handle: handle,
        }
    }

    pub fn try_recv(&self) -> Option<LoadResult> {
        self.result_rx.try_recv().ok()
    }
}

fn load_file(path: &PathBuf) -> Result<Vec<Value>, String> {
    let content = fs::read_to_string(path).map_err(|e| e.to_string())?;
    parse_json(&content)
}

fn load_stdin() -> Result<Vec<Value>, String> {
    let mut content = String::new();
    io::stdin()
        .read_to_string(&mut content)
        .map_err(|e| e.to_string())?;
    if content.is_empty() {
        return Err("Empty input is not valid JSON".to_string());
    }
    parse_json(&content)
}

fn parse_json(input: &str) -> Result<Vec<Value>, String> {
    // simd-json requires mutable input buffer
    let mut input_bytes = input.as_bytes().to_vec();

    // Try single JSON value first using simd-json
    if let Ok(value) = simd_json::to_owned_value(&mut input_bytes) {
        return Ok(vec![value]);
    }

    // Fall back to JSONL
    let mut values = Vec::new();
    for (line_num, line) in input.lines().enumerate() {
        let trimmed = line.trim();
        if trimmed.is_empty() {
            continue;
        }
        let mut line_bytes = trimmed.as_bytes().to_vec();
        let value = simd_json::to_owned_value(&mut line_bytes)
            .map_err(|e| format!("Invalid JSON on line {}: {}", line_num + 1, e))?;
        values.push(value);
    }
    if values.is_empty() {
        return Err("No valid JSON found in input".to_string());
    }
    Ok(values)
}