env.dev

jq Cheat Sheet — Filter & Transform JSON

jq commands for filtering, transforming, and merging JSON: filters, pipes, select, map/reduce, path expressions, output formatting, plus jq 1.7 gotchas.

By env.dev Updated

jq is a command-line JSON processor: you write a filter, pipe JSON through it, and get JSON (or raw text) back. The fastest paths are jq '.field' to pull a value, jq '.[]' to iterate an array, and jq -r to drop the surrounding quotes. This cheat sheet is grouped by what you are actually trying to do — read a field, filter records, reshape objects, aggregate, format output — with a working example in every row. For the long-form walkthrough, see the jq guide; for the format itself, the JSON cheat sheet.

Key takeaways

  • jq '.a.b' reads a path; add ? (.a.b?) to suppress errors on missing or wrongly-typed keys.
  • -r only strips quotes when the filter's top-level result is a string — strings nested inside arrays or objects stay quoted. Use @tsv or @csv for tabular output.
  • // supplies a fallback only when the left side is null or false .count // 0 leaves a real 0 untouched.
  • map(f) rewraps results into an array; .[] | f emits a separate output per element.

Basic Filters

FilterDescription
.Identity — pretty-print the entire input
.fooAccess object key "foo"
.foo.barAccess nested key "bar" inside "foo"
.foo?Access key "foo", suppressing errors if missing
."hyphenated-key"Access keys that contain special characters
.[]Iterate over all elements of an array or object
.[0]Access first element of an array
.[-1]Access last element of an array
.[2:5]Slice array from index 2 to 4 (exclusive end)

How Do You Pipe and Combine Filters?

ExpressionDescription
.foo | .barPipe — feed output of left filter into right filter
.foo, .barComma — output both values (multiple outputs)
.[] | .nameIterate array and extract "name" from each element
[.[] | .name]Wrap iterated results back into an array
{name: .foo, id: .bar}Construct a new object from selected fields
[.[] | {name, age}]Project specific fields from an array of objects

Comparison and Logic Operators

OperatorDescription
==, !=Equality and inequality
<, <=, >, >=Numeric/string comparison
and, or, notBoolean logic operators
if .x then .y else .z endConditional expression
// "default"Alternative operator — use right value if left is null or false
.x // emptySuppress null values (emit nothing instead)

How Do You Filter and Select Elements?

ExpressionDescription
select(.age > 30)Keep elements where condition is true
select(.name == "Alice")Keep elements matching exact value
select(.tags | contains(["go"]))Keep elements whose tags array includes "go"
map(select(.active))Filter an array to only active items
limit(3; .[])Take only the first 3 outputs
first(.[])Take the first output only
last(.[])Take the last output only
.[] | select(.price) | .priceExtract field only from objects that have it

String Operations

ExpressionDescription
"Hello, \(.name)!"String interpolation using \( )
ascii_downcaseConvert to lowercase
ascii_upcaseConvert to uppercase
split("-")Split string into array on delimiter
join(", ")Join array into string with delimiter
test("^foo")Test if string matches regex (returns boolean)
match("([0-9]+)")Return regex match object with offset/length
capture("(?<y>[0-9]{4})")Named capture groups returned as object
gsub("old"; "new")Replace all occurrences of a regex
sub("old"; "new")Replace first occurrence of a regex
ltrimstr("prefix")Remove prefix from string
rtrimstr("suffix")Remove suffix from string
lengthString length (also works on arrays/objects)
startswith("http")Check if string starts with value
endswith(".json")Check if string ends with value

Array Operations

ExpressionDescription
map(.price * 2)Apply expression to every element
map_values(. + 1)Apply expression to every value in an object
sortSort array (numbers/strings)
sort_by(.name)Sort array of objects by a key
reverseReverse an array
uniqueRemove duplicates
unique_by(.id)Remove duplicates by a key
flattenFlatten nested arrays one level
flatten(2)Flatten up to 2 levels deep
group_by(.type)Group array of objects by key (returns nested arrays)
min_by(.price)Object with minimum value for key
max_by(.price)Object with maximum value for key
addSum an array of numbers, or concatenate strings/arrays
any(. > 5)True if any element satisfies condition
all(. > 0)True if all elements satisfy condition
indices("foo")All indices where value appears
[., .[]] | lengthLength of the array

Object Operations

ExpressionDescription
keysGet array of object keys (sorted)
keys_unsortedGet array of object keys (original order)
valuesGet array of object values
has("name")Check if object has key (returns boolean)
in({"a":1})Check if input key exists in given object
to_entriesConvert {k:v} to [{key:k, value:v}, ...]
from_entriesConvert [{key:k, value:v}] back to {k:v}
with_entries(select(.value > 0))Filter object key-value pairs
with_entries(.key |= "prefix_" + .)Transform all keys
. + {newkey: "val"}Add or overwrite a key in an object
del(.unwanted)Remove a key from an object
. * {a: {b: 2}}Recursive merge (deep merge)

Reduce and Aggregation

ExpressionDescription
[.[] | .price] | addSum a specific field across array of objects
lengthCount elements in array or keys in object
[.[] | .score] | add / lengthCalculate average of a field
reduce .[] as $x (0; . + $x)Custom reduction — sum example
reduce .[] as $x ({}; . + {($x.k): $x.v})Build object from array using reduce
group_by(.type) | map({type: .[0].type, count: length})Count items per group
[.[] | .amount] | [min, max]Get min and max of a field

Type Functions and Conversions

ExpressionDescription
typeReturn type as string: "object", "array", "string", "number", "boolean", "null"
tostringConvert value to string
tonumberConvert string to number
arrays, objects, strings, numbers, booleans, nullsSelect only values of that type
notBoolean negation
emptyProduce no output (useful in conditionals)
nullLiteral null value
error("msg")Abort with error message
try .foo catch "fallback"Try expression, use fallback on error
try .fooSilently suppress errors (same as .foo?)

Output Formatting

Flag / FilterDescription
-r (--raw-output)Output raw strings without JSON quotes
-c (--compact-output)Single-line compact JSON output
-S (--sort-keys)Sort object keys alphabetically in output
-e (--exit-status)Exit 1 if last output is false or null
-n (--null-input)Don't read input — useful with --argjson or inputs
-s (--slurp)Read entire input into single array
@csvFormat array as CSV row
@tsvFormat array as TSV row
@htmlHTML-escape a string
@uriPercent-encode a string for URLs
@base64Base64-encode a string
@base64dBase64-decode a string
@jsonSerialize value as JSON string

How Do You Pass External Variables into jq?

FlagDescription
--arg name valuePass a string variable: jq --arg v "hello" '$v'
--argjson name valuePass a JSON value: jq --argjson n 42 '$n + 1'
--slurpfile name fileLoad JSON file into variable as array
--rawfile name fileLoad file contents as raw string
--jsonargsTreat remaining args as JSON values (accessible via $ARGS.positional)
$ENV.VAR_NAMERead environment variable inside jq expression
env.VAR_NAMEAlternative syntax for environment variables

Recursive Descent and Path Expressions

ExpressionDescription
..Recursively descend through all values
.. | .name? // emptyFind all "name" fields at any depth
.. | objects | select(has("id"))Find all objects with an "id" key anywhere
path(..) | select(length > 0)List all paths in the document
getpath(["a","b"])Get value at path .a.b
setpath(["a","b"]; 42)Set value at path .a.b to 42
delpaths([["a","b"]])Delete the path .a.b
leaf_pathsList all paths to leaf (non-container) values

What's New in jq 1.7 (and 1.7.1)?

jq 1.7 shipped on 6 September 2023 — the first release in roughly five years, since 1.6 in late 2018 — followed by the bug-fix 1.7.1 on 13 December 2023. If your distro still pins 1.6, these builtins will fail with jq: error: pick/1 is not defined; check with jq --version before relying on them.

Added in 1.7What it doesExample
pick(paths)Project only the listed paths, dropping everything elsejq 'pick(.id, .user.name)'
absAbsolute value while preserving the literal numberjq 'map(abs)' (e.g. [-3,4] → [3,4])
--raw-output0Like -r but NUL-separated, safe to pipe to xargs -0jq --raw-output0 '.[].path' | xargs -0 rm

The headline behavior change is literal number preservation: 1.7 keeps the original text of a number that is never mutated, so a 19-digit Twitter/X snowflake ID like 1234567890123456789 survives round-tripping instead of being rounded to a float (1.6 returned 1234567890123456800). The moment you do arithmetic on it, IEEE 754 double precision applies again — so add nothing if you only need to copy an ID through. The 1.7 source for abs is literally if . < 0 then - . else . end.

Common Gotchas

SymptomCauseFix
jq: error: Cannot index string with "x"A key is missing or the value is the wrong typeAdd ? to the access: .a.x? or guard with select(type=="object")
-r still prints quotesThe top-level result is not a string (it is an array/object)Emit strings one per line (.[] | tostring) or use @tsv / @csv
.count // 0 returns 0 for an existing false// also substitutes on false, not just nullUse if has("count") then .count else 0 end when false is valid
Large integer IDs change valuejq < 1.7 parses every number as a 64-bit doubleUpgrade to 1.7+, or read IDs as strings from the source
jq eats stdin and hangsFilter uses input/inputs or -n without supplying dataPass -n only when generating; otherwise pipe real input

For building requests or piping jq output straight into an API call, pair it with the curl cheat sheet jq -c compact output is what you want for a --data body.

Frequently Asked Questions

How do you pretty-print JSON with jq?

Pipe your JSON into jq with no filter: echo '{"a":1}' | jq . — This formats the JSON with indentation and syntax highlighting.

How do you extract a field from every object in a JSON array?

Use the pattern jq '.[].fieldname' or jq '[.[] | .fieldname]' to collect results back into an array. For example: jq '[.[] | .name]' extracts the "name" field from every object.

What is the difference between map() and .[] | expression?

They produce the same results, but map(f) wraps the output back into an array automatically. Using .[] | f produces multiple separate outputs. Use map() when you want an array result; use .[] | f when piping into further filters.

How do you handle null or missing keys in jq?

Use the alternative operator // to provide defaults: .name // "unknown". Use the optional operator ? to suppress errors: .foo? avoids an error if the input is not an object. Use select(. != null) to filter out nulls.

How do you merge two JSON files with jq?

Use jq -s '.[0] * .[1]' file1.json file2.json — the --slurp flag reads both files into an array, and the * operator performs a recursive (deep) merge. For shallow merge, use + instead of *.

How do you convert JSON to CSV with jq?

First extract headers, then format rows: jq -r '(.[0] | keys_unsorted) as $k | $k, (.[] | [.[$k[]]] ) | @csv' data.json. The @csv formatter handles quoting and escaping automatically.

Why does jq round my large integer IDs, and how do I stop it?

jq before 1.7 parses every number as a 64-bit IEEE 754 double, so a 19-digit ID like 1234567890123456789 becomes 1234567890123456800. jq 1.7 (released September 2023) preserves the literal text of any number you do not mutate, fixing the round-trip. If you cannot upgrade, read the ID as a string at the source instead of letting jq parse it as a number.

Why does jq -r still wrap my output in quotes?

The -r (--raw-output) flag only writes raw text when the filter's top-level result is a string. A string nested inside an array or object is still serialized as JSON with quotes. Project to top-level strings (.[] | .name) or use @tsv / @csv for tabular output.

Was this helpful?