SkillAgentSearch skills...

Jonq

Query JSON with SQL-like syntax. A readable jq alternative that generates pure jq under the hood. Table, CSV, YAML output. Interactive REPL. Pipes from curl, streams NDJSON logs.

Install / Use

/learn @duriantaco/Jonq

README

<div align="center"> <img src="docs/source/_static/jonq.png" alt="jonq — SQL-like JSON query tool for the command line" width="200"/>

jonq — query JSON with SQL-like syntax from the terminal

A readable alternative to jq for JSON extraction, filtering, and exploration

PyPI version Python Versions CI tests Documentation Status License: MIT Skylos Grade

</div>

What is jonq?

jonq is a command-line JSON query tool that lets you select, filter, group, and reshape JSON data using SQL-like syntax instead of raw jq. It generates pure jq under the hood, so you get jq's speed with a syntax you can actually remember.

# Instead of: jq '.[] | select(.age > 30) | {name, age}'
jonq data.json "select name, age if age > 30" -t
name    | age
--------|----
Alice   | 35
Charlie | 42

jonq is not a database. It is a readable jq frontend for exploring, extracting, and reshaping JSON. If you need joins, window functions, or large-scale analytics, shape the JSON with jonq first and then hand it to DuckDB, Polars, or Pandas.


Use jonq when you need to

  • Query JSON from APIs, config files, or log streams in the terminal
  • Explore unfamiliar JSON with the built-in path explorer
  • Write readable jq one-liners in shell scripts and CI pipelines
  • Filter, aggregate, or reshape nested JSON without memorizing jq syntax
  • Stream and filter NDJSON log output in real time

Use something else when you need

  • Tabular analytics — DuckDB, Polars, Pandas
  • Joins across files — a database or dataframe engine
  • Large-scale ETL — tools built for analytical pipelines

Rule of thumb: if the problem is still "I need to understand this JSON", jonq is a good fit. If the problem has become relational analytics, move to a database.

Features at a glance

| Category | What you can do | Example | |-------------------|-----------------|---------| | Selection | Pick fields | select name, age | | Wildcard | All fields | select * | | DISTINCT | Unique results | select distinct city | | Filtering | and / or / not / between / contains / in / like | if age > 30 and city = 'NY' | | IS NULL | Null checks | if email is not null | | Aggregations | sum avg min max count | select avg(price) as avg_price | | COUNT DISTINCT| Unique counts | select count(distinct city) as n | | Grouping | group by + having | ... group by city having count > 2 | | Ordering | sort <field> [asc\|desc] | sort age desc | | LIMIT | Standalone limit | select * limit 10 | | CASE/WHEN | Conditional expressions | case when age > 30 then 'senior' else 'junior' end | | COALESCE | Null fallback | coalesce(nickname, name) as display | | String concat | + or \|\| | first \|\| ' ' \|\| last as full_name | | Nested arrays | from [].orders or inline paths | select products[].name ... | | String funcs | upper lower length trim | select upper(name) as name_upper | | Math funcs | round abs ceil floor | select round(price) as price_r | | Type casting | int float str type | select int(price) as price | | Date/time | todate fromdate date | select todate(ts) as date | | Inline maths | Field expressions | age + 10 as age_plus_10 | | Table output | Aligned terminal tables | --format table or -t | | YAML output | YAML rendering | --format yaml | | CSV / stream | --format csv, --stream | | | Follow mode | Stream NDJSON line-by-line | tail -f log \| jonq --follow "..." | | Worker reuse | Reuse jq workers for repeated filters | --watch, --stream, Python loops | | Path explorer | Inspect nested JSON paths and types | jonq data.json (no query) | | Interactive REPL | Tab completion + history | jonq -i data.json | | Watch mode | Re-run on file change | jonq data.json "select *" --watch | | URL fetch | Query remote JSON | jonq https://api.example.com/data "select id" | | Multi-file glob | Query across files | jonq 'logs/*.json' "select *" | | Auto stdin | Auto-detect piped input | curl ... \| jonq "select id" | | Auto NDJSON | Auto-detect line-delimited JSON | No flag needed | | Shell completions | Bash/Zsh/Fish completions | jonq --completions bash | | Explain mode | Show query breakdown + jq filter | --explain | | Timing | Execution timing | --time | | Fuzzy suggest | Typo correction for fields | Suggests similar field names | | Colorized output | Syntax-highlighted JSON in terminal | Auto when TTY |


Why Jonq?

Jonq vs raw jq

| Task | Raw jq filter | jonq one-liner | |------|------------------|--------------------| | Select specific fields | jq '.[]&#124;{name:.name,age:.age}' | jonq data.json "select name, age" | | Filter rows | jq '.[]&#124;select(.age > 30)&#124;{name,age}' | ... "select name, age if age > 30" | | Sort + limit | jq 'sort_by(.age) &#124; reverse &#124; .[0:2]' | ... "select name, age sort age desc 2" | | Standalone limit | jq '.[0:5]' | ... "select * limit 5" | | Distinct values | jq '[.[].city] &#124; unique' | ... "select distinct city" | | IN filter | jq '.[] &#124; select(.city=="NY" or .city=="LA")' | ... "select * if city in ('NY', 'LA')" | | NOT filter | jq '.[] &#124; select((.age > 30) &#124; not)' | ... "select * if not age > 30" | | LIKE filter | jq '.[] &#124; select(.name &#124; startswith("Al"))' | ... "select * if name like 'Al%'" | | Uppercase | jq '.[] &#124; {name: (.name &#124; ascii_upcase)}' | ... "select upper(name) as name" | | Count items | jq 'map(select(.age>25)) &#124; length' | ... "select count(*) as over_25 if age > 25" | | Count distinct | jq '[.[].city] &#124; unique &#124; length' | ... "select count(distinct city) as n" | | Group & count | jq 'group_by(.city) &#124; map({city:.[0].city,count:length})' | ... "select city, count(*) as count group by city" | | Group & HAVING | jq 'group_by(.city) &#124; map(select(length>2)) &#124; ...' | ... "select city, count(*) group by city having count > 2" | | Field expression | jq '.[] &#124; {name, age_plus: (.age + 10)}' | ... "select name, age + 10 as age_plus" | | CASE/WHEN | jq '.[] &#124; if .age>30 then "senior" else "junior" end' | ... "select case when age > 30 then 'senior' else 'junior' end as level" | | COALESCE | jq '.[] &#124; {d: (.nick // .name)}' | ... "select coalesce(nickname, name) as display" | | IS NULL | jq '.[] &#124; select(.email != null)' | ... "select * if email is not null" | | String concat | jq '.[] &#124; {f: (.first + " " + .last)}' | ... "select first &#124;&#124; ' ' &#124;&#124; last as full" | | Type cast | jq '.[] &#124; {p: (.price &#124; tonumber)}' | ... "select float(price) as p" | | Date convert | jq '.[] &#124; {d: (.ts &#124; todate)}' | ... "select todate(ts) as d" |

Take-away: a single jonq string replaces many pipes and brackets while still producing pure jq under the hood.


Where jonq fits

  • Use jonq when the source of truth is still raw JSON and you need to inspect fields, paths, filters, or nested values quickly.
  • Use raw jq when you already know the exact jq filter you want and do not need the friendlier syntax.
  • Use DuckDB / Polars / Pandas after the JSON has become a tabular analytics problem.

TL;DR: jonq is the "understand and shape this JSON" step, not the database step.


Installation

Supported Platforms: Linux, macOS, and Windows with WSL.

Prerequisites

  • Python 3.9+
  • jq command line tool installed (https://stedolan.github.io/jq/download/)

Setup

From PyPI

pip install jonq

From source

git clone https://github.com/duriantaco/jonq.git
cd jonq && pip install -e .

Quick Start

# Create a simple JSON file
echo '[{"name":"Alice","age":30,"city":"New York"},{"name":"Bob","age":25,"city":"LA"}]' > data.json

# Select fields
jonq data.json "select name, age if age > 25"
# Output: [{"name":"Alice","age":30}]

# Table output
jonq data.json "select name, age, city" -t

# Pipe from stdin (no '-' needed)
curl -s https://api.example.com/data | jonq "select id, name" -t

# Conditional expressions
jonq data.json "select name, case when age > 28 then 'senior' else 'junior' end as level" -t

# Null handling
jonq data.json "select coalesce(nickname, name) as display"

# Type casting
jonq data.json "select name, str(age) as age_str"

# String concatenation
jonq data.json "select name || ' (' || city || ')' as label"

# YAML output
jonq data.json "select name, age" -f yaml

# See what jq jonq generates
jonq data.json "select name, age if age > 25" --explain

Query Syntax

select [distinct] <fields> [from <path>] [if <condition>] [group by <fields> [having <condition>]] [sort <field> [asc|desc]] [limit N]

Where:

  • distinct - Optional, returns unique rows
  • <fields> - Comma-separated: fields, aliases, CASE/WHEN, coalesce(), functions, aggregations, expressions
  • from <path> - Optional source path for nested data
  • if <condition> - Optional filter (supports =, !=, >, <, >=, <=, and, or, not, in, like, between, contains, is null, is not null)
  • `group
View on GitHub
GitHub Stars43
CategoryData
Updated1d ago
Forks2

Languages

Python

Security Score

95/100

Audited on Mar 27, 2026

No findings