Overview
jq is a filter language for JSON on the command line. Examples use jq 1.7 and this input unless stated:
[{"name":"alice","age":30,"tags":["admin","user"]},
{"name":"bob","age":25,"tags":["user"]},
{"name":"carol","age":35,"tags":["admin"]}]For JSON inside Postgres see postgres-jsonb; for fetching the JSON see curl-flags.
Select
Select with .[] and select(), and wrap in [...] when you need an array.
| Filter | Result |
|---|---|
.[0], .[-1], .[1:3] | First, last, slice. |
.[].name | "alice", "bob", "carol" as separate outputs. |
[.[].name] | One array of names. |
.["user-name"] | Key with special characters. |
.foo? | Suppress errors when .foo is the wrong type. |
.. | Every value, recursively. |
keys, length, type, has("name") | Introspection. |
.[] | select(.age > 28) | Keep matching elements. |
map(select(.tags | contains(["admin"]))) | Users with the admin tag. |
first(.[] | select(.age > 28)) | Stop at the first match. |
any(.[]; .age > 33), all(.[]; .age > 20) | Boolean tests. |
Transform
Prefer map and to_entries over manual loops.
| Filter | Result |
|---|---|
{n: .name, a: .age} | Build an object. |
map({name}), map(pick(.name, .age)) | Keep chosen fields (pick needs 1.7). |
map(del(.tags)) | Remove a field. |
map(. + {senior: (.age > 30)}) | Add a field. |
map(.tags) | flatten, flatten(1) | Flatten fully, or one level. |
map(.age |= . + 1) | Update in place. |
sort_by(.age), sort_by(.age) | reverse | Ascending, descending. |
group_by(.age), unique_by(.name) | Group, deduplicate. |
min_by(.age), max_by(.age) | Extremes. |
[.[].age] | add / length | Average. |
map(.tags[]) | unique | Distinct tags. |
to_entries, from_entries, with_entries(f) | Object to key/value pairs and back. |
reduce .[] as $u (0; . + $u.age) | Fold. |
[limit(2; .[])] | First two outputs. |
map(.name | ascii_upcase), test("^a"), sub("a";"A") | String and regex helpers. |
walk(f), paths, getpath(["a","b"]) | Walk nested data. |
Control flow
Use // for defaults and try only when an error is expected.
| Filter | Result |
|---|---|
.x // "default" | Right side when .x is null, false, or empty. |
if .age > 30 then "senior" else "junior" end | Conditional. |
try .x catch "missing" | Recover from an error. |
if .type == "user" then .email else empty end | empty emits nothing. |
. as $in | ... | Bind the input to a variable. |
Formats
Combine with -r so the output is not JSON-quoted.
| Filter | Output |
|---|---|
@csv, @tsv | [.name,.age] | @csv gives "alice",30. |
@uri, @html, @sh | URL-encode, HTML-escape, shell-quote a string. |
@base64, @base64d | Encode, decode. |
tojson, fromjson | Value to JSON string, and back. |
CLI flags
Use -r when output feeds a shell variable and -e when it feeds a test.
| Flag | Effect |
|---|---|
-r / -j | Raw strings, with / without trailing newline. |
-c / -S / --tab | Compact / sorted keys / tab indent. |
-e | Exit status 1 when the last output is null or false. |
-n | No input; use with inputs or --arg. |
-s | Slurp all inputs into one array. |
--arg n v / --argjson n json | Pass a string / a JSON value as $n. |
--slurpfile n f | Load a file’s JSON values into $n. |
jq has no in-place flag. Write to a temp file and move it, or use sponge:
jq '.version = "2.0"' pkg.json > tmp && mv tmp pkg.json
jq -s '.[0] * .[1]' base.json patch.json # deep-merge two files
jq -s add part1.json part2.json # concatenate arrays from several files
jq -r '.[] | [.name, .age] | @csv' users.json # CSV rows
jq --arg n "$NAME" '.[] | select(.name == $n)' # shell value in a filterGotchas
Avoid these; each yields null or several outputs where you expected one value.
| Gotcha | Fix |
|---|---|
A missing key yields null, not an error. | Use has("k") to tell missing from null. |
// also fires on false and on empty output, but not on 0 or "". | Test explicitly for those values. |
.[].x emits many values, and .a,.b is two outputs. | Wrap in [...] for an array. |
-r only unquotes strings; arrays still print as JSON. | jq -r '.[]' for one item per line. |
| Number literals over 2^53 survive untouched in jq 1.7+, but arithmetic uses doubles. | Keep big IDs as strings. |
sort_by on mixed types orders by type, not value. | Normalize with tostring or tonumber first. |
Comments start with #; // is the alternative operator. | Do not use // as a comment. |