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
FormIf 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

FilterInputOutputShape
map(f)array or objectalways an array[.[] | f]
map_values(f)array or objectsame 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

FilterEmits
..the input and every nested value, containers included
recurseidentical 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
pathsevery 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.