Readable jq for JSON extraction and exploration
Project description
jonq - readable JSON queries for the terminal
A jq-powered CLI for inspecting, filtering, and reshaping JSON without writing raw jq
What jonq is
jonq is a command-line JSON query tool. It lets you write readable, SQL-like queries such as:
jonq users.json "select name, age if age > 30" -t
Instead of raw jq:
jq '.[] | select(.age > 30) | {name, age}' users.json
jonq compiles your query to jq and executes it with a reusable jq worker. It is useful when you need to understand an unfamiliar JSON payload, extract fields, filter rows, flatten nested arrays, or turn JSON into table, CSV, JSONL, YAML, or raw scalar output.
jonq is not a database, ETL framework, or analytics engine. It is a JSON exploration and shaping tool for terminal workflows.
When to use it
Use jonq when you need to:
- inspect an API response, config file, generated JSON, or log payload
- select and rename fields without remembering jq object syntax
- filter JSON with readable conditions
- query nested objects and arrays
- produce table, CSV, JSONL, YAML, raw scalar, or compact JSON output
- run the same query in shell scripts, CI, or Python code
- follow NDJSON logs line-by-line
Use another tool when you need:
- exact jq language control: use raw
jq - Python expressions over JSON: use
jello - grep-friendly flattened assignment lines: use
gron - joins, window functions, or relational analytics: use a database or analytics engine
- production ETL, scheduling, or connectors: use an ETL system
Install
jonq requires Python 3.9+ and the jq command-line tool.
pip install jonq
From source:
git clone https://github.com/duriantaco/jonq.git
cd jonq
pip install -e .
Check that jq is available:
jq --version
Quick Start
Create a sample file:
cat > users.json <<'JSON'
[
{"id": 1, "name": "Alice", "age": 30, "city": "New York"},
{"id": 2, "name": "Bob", "age": 25, "city": "Los Angeles"},
{"id": 3, "name": "Charlie", "age": 35, "city": "Chicago"}
]
JSON
Select fields:
jonq users.json "select name, age"
Filter rows:
jonq users.json "select name, age if age > 30"
Print raw values for shell pipelines:
jonq users.json "select name" -r
Render a table:
jonq users.json "select name, city, age sort age desc" -t
Get unique values:
jonq users.json "select distinct city"
Aggregate:
jonq users.json "select count(*) as total, avg(age) as avg_age"
See what jq will run:
jonq users.json "select name if age > 30" --explain
Query Syntax
select [distinct] <fields>
[from <path>]
[if|where <condition>]
[group by <fields> [having <condition>]]
[sort <field> [asc|desc]]
[limit N]
Examples:
jonq users.json "select *"
jonq users.json "select name as full_name, age"
jonq users.json "select name if city in ('New York', 'Chicago')"
jonq users.json "select name where age > 30"
jonq users.json "select name if not age > 30"
jonq users.json "select name if name like 'Al%'"
jonq users.json "select name if age between 25 and 35"
jonq users.json "select city, count(*) as count group by city"
jonq users.json "select city, avg(age) as avg_age group by city having avg_age > 30"
jonq users.json "select name, age sort age desc limit 2"
Fields and Expressions
Select nested fields with dot notation:
jonq users.json "select profile.email, profile.address.city"
Select from nested arrays with from:
jonq orders.json "select id, total from orders"
jonq users.json "select order_id, price from [].orders if price > 100"
Use array indexes:
jonq users.json "select name, orders[0].item as first_order"
Use functions and expressions:
jonq users.json "select upper(name) as name, str(age) as age"
jonq users.json "select name || ' (' || city || ')' as label"
jonq users.json "select age * 2 + 3 as score"
jonq users.json "select coalesce(nickname, name) as display"
jonq users.json "select case when age > 30 then 'senior' else 'junior' end as segment"
Common functions:
| Category | Functions |
|---|---|
| Strings | upper, lower, length, trim, ltrim, rtrim |
| Math | round, abs, ceil, floor |
| Casting | int, float, str, string, type |
| Dates | todate, fromdate, date, timestamp |
| JSON/arrays | keys, values, tojson, fromjson, reverse, sort, unique, flatten |
| Nulls | coalesce, is null, is not null |
Output Formats
JSON is the default:
jonq users.json "select name, age"
Table:
jonq users.json "select name, age, city" -t
jonq users.json "select name, age, city" --format table
CSV:
jonq users.json "select name, age" --format csv > users.csv
JSONL:
jonq users.json "select name, age" --format jsonl > users.jsonl
YAML:
jonq users.json "select name, age" --format yaml
Raw scalar values:
jonq users.json "select name" -r
Input Sources
Local file:
jonq data.json "select id, name"
Piped stdin:
curl -s https://api.example.com/users | jonq "select id, name" -t
cat data.json | jonq "select id, name where active = true"
cat data.json | jonq - "select id, name"
URL:
jonq https://api.example.com/users.json "select id, email"
Glob:
jonq 'logs/*.json' "select * if level = 'error'"
NDJSON file:
jonq app.ndjson "select level, message if level = 'error'"
Follow live NDJSON from stdin:
tail -f app.ndjson | jonq --follow "select level, message if level = 'error'" -t
Inspect Before Querying
Run jonq with no query to inspect shape, fields, sample values, and suggested queries:
jonq data.json
Example output:
data.json
Root: array of objects (sampled 3 items)
Fields:
id number sample: 1
name string sample: "Alice"
age number sample: 30
city string sample: "New York"
Sample:
{
"id": 1,
"name": "Alice",
"age": 30,
"city": "New York"
}
Suggested queries:
jonq data.json "select id, name, city" -t
jonq data.json "select name" -r
jonq data.json "select city, count(*) as count group by city" -t
Streaming and Watch Modes
Streaming mode processes root-array JSON in chunks:
jonq large.json "select id, name if active = true" --stream
Streaming is for row-wise queries. It intentionally rejects queries that require global input state, including group by, sort, distinct, limit, count, sum, avg, min, max, and count(distinct ...).
Watch mode reruns a query when a file changes:
jonq data.json "select name, age" --watch
Interactive mode provides history and field-aware completion:
jonq -i data.json
Python API
Use query(...) when you want Python data back:
from jonq import query
data = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25},
]
rows = query(data, "select name if age > 26")
print(rows)
Use execute(...) when you want text output plus metadata:
from jonq import execute
result = execute(data, "select name, age", format="jsonl")
print(result.text)
print(result.compiled.jq_filter)
Compile once and reuse:
from jonq import compile_query, query
compiled = compile_query("select name if age > 25")
print(query([{"name": "Alice", "age": 30}], compiled))
Async helpers are also available: query_async(...) and execute_async(...).
CLI Options
| Option | Description |
|---|---|
-f, --format {json,jsonl,csv,table,yaml} |
Output format |
-t, --table |
Shorthand for --format table |
-r, --raw, --raw-output |
Print scalar values without JSON quoting |
-s, --stream |
Chunk-safe streaming for root-array JSON |
--ndjson |
Force NDJSON mode |
--follow |
Process NDJSON from stdin line-by-line |
-n, --limit N |
Limit rows after query execution |
-o, --out PATH |
Write output to a file |
--jq |
Print the generated jq filter and exit |
--explain |
Show parsed query details and generated jq |
--time |
Print parse/execute/format timing to stderr |
-p, --pretty |
Pretty-print JSON output |
-w, --watch |
Rerun when the input file changes |
--no-color |
Disable terminal color |
--completions {bash,zsh,fish} |
Print shell completions |
--version |
Print version |
-i FILE, --interactive FILE |
Start the REPL |
Shell Completions
# Bash
eval "$(jonq --completions bash)"
# Zsh
eval "$(jonq --completions zsh)"
# Fish
jonq --completions fish > ~/.config/fish/completions/jonq.fish
Troubleshooting
-
jqis not found: Install jq and make sure it is onPATH. -
No query was provided: Run
jonq data.jsonto inspect the file, or pass a query such asjonq data.json "select *". -
A field is missing or misspelled: jonq validates top-level fields for normal file inputs and suggests close matches.
-
Streaming mode rejected a query: Use non-streaming mode for global operations like aggregation, grouping, sorting, distinct, or limit.
-
The generated jq looks surprising: Run the same command with
--explainto see the parsed query and generated jq filter.
Known Limitations
- jonq exposes a practical subset of jq, not the full jq language.
- Streaming mode supports row-wise queries only.
- Cross-file joins, window functions, and relational analytics are out of scope.
- URL fetch is a convenience feature, not a full HTTP client.
- Very large files can still be slow if the query requires full-input state.
Documentation
- Full docs: https://jonq.readthedocs.io/en/latest/
- Syntax reference: SYNTAX.md
- Usage examples: USAGE.md
- Contributing: CONTRIBUTIONS.md
License
jonq is licensed under the MIT License. See License.
jonq depends on the jq command-line JSON processor. jq is licensed under the MIT License and is not bundled with jonq.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jonq-0.3.2.tar.gz.
File metadata
- Download URL: jonq-0.3.2.tar.gz
- Upload date:
- Size: 631.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0be64d05c14cedc2764f243af9dd748f8a0f5fa1250b6ed0c400120483762a3b
|
|
| MD5 |
2427251480450df10bbf83dcaf4c80cb
|
|
| BLAKE2b-256 |
a66c945f5d878bd56e4b95ba942a7b4bd46a056f2131c3c6ebdd77c01c4ec325
|
Provenance
The following attestation bundles were made for jonq-0.3.2.tar.gz:
Publisher:
release.yml on duriantaco/jonq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jonq-0.3.2.tar.gz -
Subject digest:
0be64d05c14cedc2764f243af9dd748f8a0f5fa1250b6ed0c400120483762a3b - Sigstore transparency entry: 1406435178
- Sigstore integration time:
-
Permalink:
duriantaco/jonq@02a41ad8fd1c4003ef91b843c6e3e946cc83d952 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/duriantaco
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@02a41ad8fd1c4003ef91b843c6e3e946cc83d952 -
Trigger Event:
release
-
Statement type:
File details
Details for the file jonq-0.3.2-py3-none-any.whl.
File metadata
- Download URL: jonq-0.3.2-py3-none-any.whl
- Upload date:
- Size: 47.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
db6d73ee5283ca384bffb7c0c91f5e1c3a6fe68a40139e3e5c3a93690af217a5
|
|
| MD5 |
e7d0736d2709f1566abb209001bcd3fd
|
|
| BLAKE2b-256 |
0eeef49ac82dae55c98d03b12495529c79a6e10ca7f83b930d1db0eb1b427166
|
Provenance
The following attestation bundles were made for jonq-0.3.2-py3-none-any.whl:
Publisher:
release.yml on duriantaco/jonq
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jonq-0.3.2-py3-none-any.whl -
Subject digest:
db6d73ee5283ca384bffb7c0c91f5e1c3a6fe68a40139e3e5c3a93690af217a5 - Sigstore transparency entry: 1406435243
- Sigstore integration time:
-
Permalink:
duriantaco/jonq@02a41ad8fd1c4003ef91b843c6e3e946cc83d952 -
Branch / Tag:
refs/tags/v0.3.2 - Owner: https://github.com/duriantaco
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@02a41ad8fd1c4003ef91b843c6e3e946cc83d952 -
Trigger Event:
release
-
Statement type: