jq Cheatsheet
Mapping and Iteration
Use this jq reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
.[] versus map
.[] explodes a container into a stream of separate outputs. map(f) keeps the
array shape and applies f to each element. map(f) is exactly [.[] | f].
jq -c '.items[].id' data.json # 1 2 3, three outputs jq -c '[.items[].id]' data.json # [1,2,3], one output jq -c '.items | map(.id)' data.json # [1,2,3], identical jq -c '.items | map(.price * 2)' # [20,40,60] jq -c '.items | map(select(.cat == "x") | .id)' # [1,3]
.[] works on both arrays and objects. On an object it emits the values and
drops the keys, in insertion order.
jq -c '.meta[]' data.json # 3 jq -c '[.tags[]]' data.json # ["web","prod"] jq -cn '{a:1,b:2} | [.[]]' # [1,2], keys gone jq -cn '"abc" | .[]' # error, Cannot iterate over string
Iterating a string is an error. Use explode or split("") for characters.
.[]?, iterate only if you can
.[]? emits nothing instead of raising when the input is not iterable. It is the
right tool for ragged data and the wrong tool for data you believe is uniform.
echo '{"a":1}' | jq -c '.a[]' # error: Cannot iterate over number (1), exit 5 echo '{"a":1}' | jq -c '.a[]?' # no output, exit 0 jq -c '[.items[]?]' data.json # [] rather than an error if items is missing jq -c '[.. | .price? // empty]' # every price at any depth, errors skipped
| Form | If the input is not iterable |
|---|---|
.[] | error, exit 5 |
.[]? | emits nothing |
.foo[] | error, including when .foo is missing |
.foo[]? | emits nothing |
A missing key gives null, and null[] is still an error, so .missing[] fails
with Cannot iterate over null exactly as .number[] does. .missing[]? is the
safe read.
map versus map_values
| Filter | Input | Output | Shape |
|---|---|---|---|
map(f) | array or object | always an array | [.[] | f] |
map_values(f) | array or object | same type as the input | .[] |= f |
jq -cn '{a:1,b:2} | map(. * 10)' # [10,20], an ARRAY jq -cn '{a:1,b:2} | map_values(. * 10)' # {"a":10,"b":20}, keys kept jq -c '.items | map(.id)' data.json # [1,2,3] jq -c '.items | map_values(.id)' data.json # [1,2,3], arrays behave the same jq -c '.meta | map_values(. * 2)' data.json # {"count":6}
Two behaviors of map_values catch people out, and both come from it being defined
as .[] |= f:
# a filter producing empty DELETES the key jq -cn '{a:1,b:2} | map_values(select(. > 1))' # {"b":2} # only the FIRST output of the filter is used jq -cn '{a:1} | map_values(., .)' # {"a":1}
That first one is genuinely useful. map_values(select(...)) is how you filter an
object by value while keeping it an object.
to_entries, from_entries, with_entries
The three-filter set that turns an object into an array of {key, value} pairs so
you can use array tooling on it, and back again.
jq -c '.meta | to_entries' data.json # [{"key":"count","value":3}] jq -c '.meta | to_entries | from_entries' data.json # {"count":3} jq -c '.meta | with_entries(.key |= ascii_upcase)' # {"COUNT":3} jq -cn '{a:1,b:2} | with_entries(select(.value > 1))' # {"b":2} jq -cn '{a:1,b:2} | to_entries | map(.key)' # ["a","b"], same as keys jq -cn '[{key:"a",value:1}] | from_entries' # {"a":1} jq -cn '[{name:"a",value:1}] | from_entries' # {"a":1}, name also works jq -cn '[["a",1]] | map({key:.[0],value:.[1]}) | from_entries' # {"a":1}
- with_entries(f) is exactly to_entries | map(f) | from_entries.
- from_entries accepts key, Key, name, or Name for the key field, and
value or Value for the value. k and v are not accepted in jq 1.8, they
produce a null-key error. A non-string key is an error too, not a stringified key,
so pipe numeric keys through tostring first.
- Sorting or renaming keys, filtering by value, and swapping keys and values all go
through this trio.
# invert an object jq -cn '{a:1,b:2} | with_entries({key: (.value|tostring), value: .key})' # {"1":"a","2":"b"}
keys, values, and object shape
jq -c 'keys' data.json # ["items","meta","name","tags","version"], SORTED jq -c 'keys_unsorted' data.json # ["name","version","tags","items","meta"] jq -c '[.[]]' data.json # the values, insertion order jq -c 'length' data.json # 5, the key count jq -c '.items | length' # 3, the element count jq -c '.name | length' # 3, the character count jq -c 'has("meta")' # true jq -c 'to_entries | length' # 5
keys sorts, keys_unsorted does not. Use keys_unsorted when you are rebuilding
an object and want to preserve the original field order, and keys when you need a
stable comparison between two documents.
length is polymorphic: object key count, array element count, string character
count, absolute value of a number, and 0 for null.
Iterating deeply with .., recurse, and walk
| Filter | Emits |
|---|---|
.. | the input and every nested value, containers included |
recurse | identical to .. |
recurse(f) | the input, then f, then f|f, until f yields nothing |
walk(f) | a rebuilt copy with f applied bottom-up to every value |
paths | every path as an array, excluding the empty root path |
paths(f) | only the paths whose value satisfies f |
jq -c '[.. | numbers]' data.json # [1,10,2,20,3,30,3] jq -c '[paths(scalars)] | length' data.json # 17 jq -c 'walk(if type == "number" then . * 10 else . end)' data.json jq -cn '{"A":{"B":1}} | walk(if type == "object" then with_entries(.key |= ascii_downcase) else . end)' # {"a":{"b":1}} jq -cn '1 | [limit(5; recurse(. * 2))]' # [1,2,4,8,16] jq -cn '[{a:{b:[1,2]}} | .. | arrays]' # [[1,2]]
- .. and paths also visit containers, so pair them with a type filter
(numbers, strings, scalars, objects, arrays) or you will process the
parents too.
- leaf_paths was removed. Use paths(scalars).
- walk rebuilds from the leaves up, so a filter on an object sees children that
have already been transformed.