jq is a command-line tool that reads JSON, runs it through a filter you write, and prints JSON back out. The mental model that makes it click: a jq program is a pipeline. Data enters on the left, each stage transforms it, and what falls out the right is your answer. Once pipes feel natural, the rest of jq is just a vocabulary of filters to drop into that pipeline.
Everything below runs against one response, a short list of users from an imaginary API. Assume it is saved as users.json and piped into jq.
Identity and field access
The simplest filter is ., the identity. It passes its input straight through, which is why jq . is the usual way to pretty-print a file. From there, a key after the dot reaches into an object.
Indexing an array uses [n], and a bare [] explodes an array into a stream of its elements, one value at a time. That stream is the input the next stage in a pipe will see.
Pipes
The pipe | feeds the output of one filter into the next, exactly like a shell pipe but inside jq. This lets you drill in stages. The two commands below are equivalent; the piped form reads more clearly as the paths grow.
map and select
map(f) applies a filter to every element of an array and collects the results back into an array.select(cond) passes its input through only when the condition holds, and drops it otherwise, which makes it the filter for keeping some elements and discarding the rest.
To filter a list, explode it with [], run each element through select, and the stream that survives is your result. This keeps only the admins.
map and select combine cleanly. Inside a map, use select to keep some elements while staying in array form, which is often what you want feeding the next stage.
keys, values and length
These three answer the everyday structural questions. keyslists an object’s keys, sorted.length counts: the number of elements in an array, keys in an object, or characters in a string.
The companion to keys is [.[]], or the built-in over objects: piping an object to .[] streams its values, so .users[0] | [.[]]collects that user’s values into an array.
Building objects
Curly braces build a new object, and inside them you assign fields from the input. This is how you reshape a fat record down to just the fields you care about, or rename them.
jq has a shorthand: when the key matches the field name, {name} means {name: .name}. So {name, role} plucks those two fields straight through. To reach nested data, path into the input on the right of the colon, as in {name: .user.name} for a document one level deeper.
Arrays from streams, and raw output
Wrapping any filter in [ ] gathers its whole output stream into a single array. This is the inverse of [], and it is how you turn a stream of results back into one JSON array.
By default jq prints strings with their quotes, which is correct JSON but awkward when you want to pipe a plain value into another shell command. The -r flag (raw output) strips the quotes, giving you bare text, one per line.
group_by and sort_by
sort_by(f) orders an array by the value a filter returns, and group_by(f) buckets adjacent equal values into sub-arrays. Both expect an array as input, so feed them .users rather than the exploded stream.
group_by returns an array of arrays, one group per distinct value. It sorts before grouping, so you do not have to. Here it splits users by role, then counts each group.
Defaults and missing fields
Real API responses have holes: a field is sometimes absent, sometimes null. Accessing a missing key in jq yieldsnull rather than an error, which is forgiving, but null values then flow downstream and can break a later stage. The alternative operator // supplies a fallback when the left side is null or false.
To test for a key before touching it, has("key") returns true or false, and the ? suffix on a path makes an operation quietly skip inputs it cannot apply to instead of erroring. Both are the tools for surviving inconsistent data without a wall of error output.
Turning objects into pairs
Sometimes you need to iterate an object’s keys and values together, or rewrite its shape. to_entries converts an object into an array of {key, value} pairs; from_entries reverses it. This is the standard idiom for transforming every field of an object.
Between the two you can remap keys, drop fields conditionally, or filter an object down, all with the array tools you already know from earlier. It is the escape hatch for object work that map alone cannot reach, because map operates on arrays.
A worked example, start to finish
Put the pieces together. Suppose you want the names of active members, in order of most logins, as plain lines. Read the pipeline left to right: take the users, keep the active members, sort by logins descending, pull the names, print raw.
That is the whole idiom of jq: small filters, joined by pipes, each doing one thing. Note sort_by(-.logins) sorts by the negated login count, which is the simplest way to get descending order for a number.
Where to go next
jq goes much deeper, with variables, functions, reduce and string interpolation, but the filters above cover the large majority of day-to-day use. If you would rather point at values than transform them, the sibling JSONPath examples cover the selection-only approach, and for the pattern-matching that often sits alongside jq, the cluster pillar is the regex cheat sheet. Living in a shell all day also means copying commands cleanly; our guide to copy and paste in the terminal covers the shortcuts that will not cancel the command you just pasted.