Processing JSON with jq
By the end of this lesson you'll be able to slice, filter, and reshape JSON straight from the terminal with jq — pulling out fields, iterating arrays, filtering with conditions, and feeding a live API response from curl into a clean, scriptable result.
Learn Processing JSON with jq in our free Command Line course — an interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Cli course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You'll Learn
1. The Identity Filter & Accessing Fields
jq is a dedicated JSON processor with its own little filter language — it is not bash. You pipe JSON into it and give it a filter wrapped in single quotes (the quotes stop the shell from touching it). The simplest filter is . , the identity filter : it returns the input unchanged and pretty-prints it. To pull out a field, write .key — for example .name . Strings come back with their JSON quotes.
2. Reaching Into Nested Objects
JSON nests objects inside objects. To dig in, just chain the keys : .a.b.c . So .address returns the whole address object, and .address.city reaches one level deeper to the city inside it. Each dot steps down one level.
3. Arrays: Index With .[0] and Iterate With .[]
For arrays, .[n] grabs a single element by its zero-based index — .[0] is the first, .[2] the third. To process every element, use .[] : it iterates , emitting each element as its own separate result. Put it after a field — .colors[] — to walk an array that lives inside an object.
4. Raw Output With -r
By default jq prints strings as valid JSON, so a name comes out as "Ada" — quotes and all. The -r ( raw ) flag strips those quotes and prints Ada . That's exactly what you want when piping a value into another command or capturing it in a shell variable. Combine -r with .[] to get a clean, plain list.
5. Piping Filters: select and map
Inside a filter, jq has its own pipe | that feeds one filter's output into the next. select(condition) passes its input through only when the condition is true, so .[] | select(.age > 30) keeps just the matching records. map(f) applies a filter to every element of an array and collects the results into a new array — map(.price * 2) doubles each price.
6. keys, length, Building Objects & curl
keys returns an object's keys as a sorted array ; length gives an array's element count, a string's character count, or an object's number of keys. You can build a brand-new object with { name: .name, id: .id } , keeping only the fields you care about. And the payoff: pipe a live response from curl straight into jq, e.g. curl -s URL | jq '.results[].name' .
🎯 Your Turn: pull a nested field, raw
Fill in the blank marked ___ using the # 👉 hint, then run it and check the Output panel matches.
🎯 Your Turn: iterate and filter
Two blanks: the filter that iterates an array, and the comparison that means "18 or more". Then run it.
Pro Tips
- 💡 Always single-quote the filter: jq '.[] | select(.age > 30)' . Without quotes the shell mangles $ , * , spaces, and parentheses before jq sees them.
- 💡 Reach for -r when piping onward: raw output drops the JSON quotes so a value slots cleanly into $(...) or another command.
- 💡 .[] streams, map() collects: use .[] with select for a flowing list; use map() when you want the result back as one array.
- 💡 Build before you read: jq has a playground at jqplay.org — paste sample JSON and tweak the filter live until the output is right.
Common Errors (and the fix)
- "jq: error: syntax error" from an unquoted filter — the shell expanded part of it. Wrap the whole filter in single quotes : jq '.name' .
- Forgetting the leading dot — it is .name , not name . A field path always starts with a dot.
- "Cannot index array with \"name\"" — you used .name on an array. Iterate first: .[] | .name (or index it, .[0].name ).
- Output still has quotes — you wanted plain text but left off -r . Add it: jq -r '.name' .
- "jq: command not found" — jq isn't installed. Install it (for example brew install jq or apt install jq ), then retry.
📋 Quick Reference
Filter
Example
What it does
Identity
jq '.'
Input unchanged, pretty-printed
Field
jq '.name'
Value of a key
Nested
jq '.a.b'
Chain keys to go deeper
Index
jq '.[0]'
One element (zero-based)
Iterate
jq '.[]'
Each element separately
Pipe
jq '.[] | .name'
Feed one filter into the next
Filter
jq 'select(.age > 30)'
Keep only matching input
Transform
jq 'map(.price * 2)'
Apply to each, return array
Raw
jq -r '.name'
Strip the JSON quotes
Keys
jq 'keys'
Object keys, sorted array
Length
jq 'length'
Array len / string len / key count
Build
jq ' { n: .name } '
Construct a new object
Frequently Asked Questions
Mini-Challenge: summarise a product list
No blanks this time — just a brief and some hints. Given a JSON array of products, count them, list their names cleanly, and rebuild the pricier ones into trimmed objects. Check your output against the example in the comments.
🎉 Lesson Complete!
- ✅ jq is a dedicated JSON processor with its own filter language — wrap filters in single quotes
- ✅ . is identity; .key and .a.b read fields; .[0] indexes and .[] iterates
- ✅ Pipe filters with | ; select(.age > 30) filters and map(.price * 2) transforms
- ✅ -r strips quotes; keys gives a sorted key array; length counts
- ✅ Build new objects with { name: .name, id: .id } and feed curl -s URL | jq '...'
- ✅ Next lesson: Package Managers — install and manage software from the command line
Practice quiz
What is jq?
- A dedicated command-line JSON processor with its own filter language
- A bash built-in command
- A web browser
- A JavaScript runtime
Answer: A dedicated command-line JSON processor with its own filter language. jq is a standalone tool for slicing, filtering, and transforming JSON. Its filter language is its own — it is not bash.
Which filter is the identity filter that passes the input through unchanged (and pretty-prints it)?
- @
- *
- .
- id
Answer: .. The single dot . is the identity filter: it returns the input as-is and jq pretty-prints it by default.
How do you access the field named name on a JSON object?
- name()
- .name
- get(name)
- ->name
Answer: .name. A field is accessed with .name, for example echo '{"name":"Ada"}' | jq '.name'.
What does the filter .[] do?
- Deletes the array
- Returns the array length
- Sorts the array
- Iterates over the elements of an array (or values of an object)
Answer: Iterates over the elements of an array (or values of an object). .[] iterates: it produces each element of an array as a separate output value.
What does the -r flag do?
- Outputs raw strings without surrounding quotes
- Reads from a file
- Repeats the filter
- Reverses the output
Answer: Outputs raw strings without surrounding quotes. -r (raw output) prints string results without the surrounding double quotes, which is ideal for shell pipelines.
Why are jq filters usually wrapped in single quotes?
- It is only a style preference with no effect
- To stop the shell from expanding or mangling characters like $, *, and spaces in the filter
- To make jq run faster
- Single quotes are required to define a variable
Answer: To stop the shell from expanding or mangling characters like $, *, and spaces in the filter. Single quotes protect the filter from the shell so jq receives it verbatim.
What does keys return when given a JSON object?
- The number of keys
- The values only
- The first key as a string
- The object's keys as a sorted array
Answer: The object's keys as a sorted array. keys returns the object's keys as an array, sorted alphabetically.
What does length return for a JSON array?
- The first element
- Always 1
- The number of elements in the array
- The array reversed
Answer: The number of elements in the array. For an array, length is the element count; for a string it is the character count; for an object it is the number of keys.
Which filter keeps only the items where age is greater than 30?
- filter(age, 30)
- select(.age > 30)
- where .age > 30
- keep(.age)
Answer: select(.age > 30). select(condition) passes its input through only when the condition is true, e.g. select(.age > 30).
What does map(.price * 2) do to an input array?
- Returns a new array with each element's price doubled
- Returns only the first price
- Sorts the array by price
- Deletes the price field
Answer: Returns a new array with each element's price doubled. map(f) applies the filter f to every element of an array and collects the results into a new array.
Continue this course
- Previous: HTTP from the Terminal: curl and wget
- Next: Package Managers