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

TaskFilterInputOutput
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 objectkeys{"a":1,"b":2}["a","b"]
Values of object[.[]]{"a":1,"b":2}[1,2]
Lengthlength[1,2,3] or "hello"3 or 5
Typetype[1,2]"array"
Raw string outputjq -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

TaskFilterNotes
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 matchfirst(.[] | select(.active))Stop after first
Count matches[.[] | select(.active)] | lengthWrap 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

TaskFilterNotes
Map over array[.[] | .name] or map(.name)Extract field from each item
Map with expressionmap(.price * .qty)Compute derived value
Add fieldmap(. + {total: (.price * .qty)})Augment each object
Remove fieldmap(del(.internal))Delete key from each
Rename fieldmap({id: .uid, name: .name})Build new object
Flatten one levelflatten(1)Nested arrays
Flatten allflattenFully flat
Unique values[.[] | .tag] | uniqueDeduplicate
Sort by fieldsort_by(.created_at)Ascending; reverse for desc
Group by fieldgroup_by(.category)Array of arrays

map(f) is shorthand for [.[] | f]. Prefer map for readability.

Conditional output

TaskFilterNotes
If-then-else.status | if . == "ok" then "pass" else "fail" endValue branch
Null default.nickname // "anon"Operator // is alternative
Optional field.address?.city? suppresses error if null
Try-catchtry .x catch "missing"Catches errors
When matchingif .type == "user" then .email else empty endempty 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

TaskFilterNotes
CSV row[.name, .email] | @csvQuoted CSV string
TSV row[.name, .email] | @tsvTab-separated
URL-encoded.value | @uriPercent-encode a string
HTML-escape.html | @htmlEscapes <>&
Base64 encode.data | @base64Standard base64
Base64 decode.encoded | @base64dDecode
Shell-safe.value | @shQuote for shell embedding
JSON string. | tojsonEmit as JSON string
Parse JSON string.raw | fromjsonParse 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

TaskCommandNotes
Process multiple filesjq '.name' file1.json file2.jsonEach file processed in turn
Merge two objectsjq -s '.[0] * .[1]' base.json patch.json-s slurps into array
Concatenate arraysjq -s 'add' part1.json part2.jsonadd reduces with +
Update in placejq '.version = "2.0"' pkg.json > tmp && mv tmp pkg.jsonjq has no in-place flag
Stream large filejq -c '.[]' large.json-c compact; one line per item
Null-separatedjq -jOmit newline between outputs
Read from variableecho "$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

  • jq outputs JSON by default, so strings are double-quoted. Use -r when passing values to shell.
  • .foo on a null returns null silently. Use select(. != 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 but map is slightly faster on large inputs because it avoids repeated iterator creation.
  • .a,.b is 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.