jarq 0.2.2

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

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> {
    // Try single JSON value first
    if let Ok(value) = serde_json::from_str::<Value>(input) {
        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 value = serde_json::from_str(trimmed)
            .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)
}