Overview
jq is a streaming JSON processor. It reads JSON from stdin or files, applies a filter expression, and writes JSON (or plain text) to stdout. This card is a recipe book for the transformations that come up repeatedly in API scripting, data pipelines, and config manipulation. For the core syntax vocabulary see jq-syntax; for getting the JSON in the first place see curl-test-patterns.
Basic extraction
| Task | Filter | Input | Output |
|---|---|---|---|
| Top-level field | .name | {"name":"Alice"} | "Alice" |
| Nested field | .address.city | {"address":{"city":"NYC"}} | "NYC" |
| Array index | .[2] | [0,1,2,3] | 2 |
| Last element | .[-1] | [1,2,3] | 3 |
| Slice | .[1:3] | [0,1,2,3] | [1,2] |
| Keys of object | keys | {"a":1,"b":2} | ["a","b"] |
| Values of object | [.[]] | {"a":1,"b":2} | [1,2] |
| Length | length | [1,2,3] or "hello" | 3 or 5 |
| Type | type | [1,2] | "array" |
| Raw string output | jq -r '.name' | {"name":"Alice"} | Alice (no quotes) |
Use -r (raw output) when passing the result to a shell variable: NAME=$(jq -r '.name' data.json).
Filtering arrays
| Task | Filter | Notes |
|---|---|---|
| Filter by field value | .[] | select(.status == "active") | Outputs each matching object |
| Filter by existence | .[] | select(.email != null) | Skip objects missing a field |
| Filter by type | .[] | select(type == "object") | Skip non-objects in mixed arrays |
| First match | first(.[] | select(.active)) | Stop after first |
| Count matches | [.[] | select(.active)] | length | Wrap in [] before length |
| Reject items | .[] | select(.status != "deleted") | Inverse filter |
| Filter nested array | .users[] | select(.roles[] == "admin") | Any role matches |
Chain select after map to filter and transform in one pass: [.[] | select(.score > 80) | .name].
Transforming and reshaping
| Task | Filter | Notes |
|---|---|---|
| Map over array | [.[] | .name] or map(.name) | Extract field from each item |
| Map with expression | map(.price * .qty) | Compute derived value |
| Add field | map(. + {total: (.price * .qty)}) | Augment each object |
| Remove field | map(del(.internal)) | Delete key from each |
| Rename field | map({id: .uid, name: .name}) | Build new object |
| Flatten one level | flatten(1) | Nested arrays |
| Flatten all | flatten | Fully flat |
| Unique values | [.[] | .tag] | unique | Deduplicate |
| Sort by field | sort_by(.created_at) | Ascending; reverse for desc |
| Group by field | group_by(.category) | Array of arrays |
map(f) is shorthand for [.[] | f]. Prefer map for readability.
Conditional output
| Task | Filter | Notes |
|---|---|---|
| If-then-else | .status | if . == "ok" then "pass" else "fail" end | Value branch |
| Null default | .nickname // "anon" | Operator // is alternative |
| Optional field | .address?.city | ? suppresses error if null |
| Try-catch | try .x catch "missing" | Catches errors |
| When matching | if .type == "user" then .email else empty end | empty produces no output |
The alternative operator // returns the right side when the left is false or null. It does not trigger on 0 or "".
Building output formats
| Task | Filter | Notes |
|---|---|---|
| CSV row | [.name, .email] | @csv | Quoted CSV string |
| TSV row | [.name, .email] | @tsv | Tab-separated |
| URL-encoded | .value | @uri | Percent-encode a string |
| HTML-escape | .html | @html | Escapes <>& |
| Base64 encode | .data | @base64 | Standard base64 |
| Base64 decode | .encoded | @base64d | Decode |
| Shell-safe | .value | @sh | Quote for shell embedding |
| JSON string | . | tojson | Emit as JSON string |
| Parse JSON string | .raw | fromjson | Parse embedded JSON string |
Combine with -r for raw output: jq -r '[.name,.email] | @csv' writes a CSV line without outer quotes.
Batch and file operations
| Task | Command | Notes |
|---|---|---|
| Process multiple files | jq '.name' file1.json file2.json | Each file processed in turn |
| Merge two objects | jq -s '.[0] * .[1]' base.json patch.json | -s slurps into array |
| Concatenate arrays | jq -s 'add' part1.json part2.json | add reduces with + |
| Update in place | jq '.version = "2.0"' pkg.json > tmp && mv tmp pkg.json | jq has no in-place flag |
| Stream large file | jq -c '.[]' large.json | -c compact; one line per item |
| Null-separated | jq -j | Omit newline between outputs |
| Read from variable | echo "$JSON" | jq '.name' | Pipe string to jq |
jq has no --in-place flag. Write to a temp file and rename. sponge from moreutils can simplify this: jq '.x = 1' f.json \| sponge f.json.
Common gotchas
jqoutputs JSON by default, so strings are double-quoted. Use-rwhen passing values to shell..fooon anullreturnsnullsilently. Useselect(. != null)upstream to catch unexpected nulls.//is the alternative operator, not a comment. Comments are not supported in jq filters.map(f)and[.[] | f]are identical in output butmapis slightly faster on large inputs because it avoids repeated iterator creation..a,.bis two separate outputs, not an array. Wrap in[]to get[.a, .b].- Integer arithmetic in jq uses IEEE 754 doubles. Numbers larger than 2^53 lose precision. Use strings for big integers.