jarq 0.8.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};

use crate::error::LoadError;

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

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>, LoadError> {
    let content = fs::read_to_string(path)?;
    parse_json(&content)
}

fn load_stdin() -> Result<Vec<Value>, LoadError> {
    let mut content = String::new();
    io::stdin().read_to_string(&mut content)?;
    if content.is_empty() {
        return Err(LoadError::EmptyInput);
    }
    parse_json(&content)
}

fn parse_json(input: &str) -> Result<Vec<Value>, LoadError> {
    // 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| LoadError::ParseLine {
                line: line_num + 1,
                message: e.to_string(),
            })?;
        values.push(value);
    }
    if values.is_empty() {
        return Err(LoadError::NoValidJson);
    }
    Ok(values)
}