What Is jq?
jq is a lightweight, portable command-line tool for parsing, filtering, and transforming JSON. Think of it as sed or awk for JSON data. If you regularly work with APIs, config files, or log aggregators, jq will save you hours every week.
It's available in every major package manager, has zero runtime dependencies, and its filter syntax — once you internalize the basics — is surprisingly elegant.
Installation
# Debian/Ubuntu
sudo apt install jq
# macOS
brew install jq
# Windows (Chocolatey)
choco install jq
The Basics: Filters and the Identity Filter
Every jq invocation takes input JSON and applies a filter. The simplest filter is . — the identity filter, which pretty-prints whatever you pass in:
echo '{"name":"lolo","version":"1.0"}' | jq '.'
Output:
{
"name": "lolo",
"version": "1.0"
}
Accessing Fields
Use dot notation to drill into objects:
curl -s https://api.github.com/repos/torvalds/linux | jq '.stargazers_count'
Chain fields with dots: .owner.login gets the nested login field inside owner.
Working with Arrays
If your JSON root is an array, use .[] to iterate over elements:
echo '[1,2,3,4]' | jq '.[]'
Combine with field access to extract a field from every object in an array:
curl -s https://api.github.com/users/torvalds/repos | jq '.[].name'
Building New Objects and Arrays
jq lets you construct new JSON from input data — this is where it gets powerful:
# Reshape each repo into a compact summary
curl -s https://api.github.com/users/torvalds/repos | \
jq '[.[] | {name: .name, stars: .stargazers_count, lang: .language}]'
The | is jq's pipe operator — it works exactly like the shell pipe but within the filter expression.
Filtering with select()
Use select() to keep only items matching a condition:
# Only repos with more than 100 stars
jq '[.[] | select(.stargazers_count > 100)]'
# Only items where language is "C"
jq '[.[] | select(.language == "C")]'
Useful Built-in Functions
| Function | What It Does | Example |
|---|---|---|
length | Count array items or string chars | .[] | length |
keys | Get object keys as array | . | keys |
has("field") | Check if key exists | select(has("email")) |
to_entries | Object → array of {key, value} | . | to_entries |
sort_by(.field) | Sort array by field value | sort_by(.name) |
group_by(.field) | Group array by field | group_by(.language) |
unique_by(.field) | Deduplicate by field | unique_by(.id) |
@csv / @tsv | Format array as CSV/TSV | [.name,.stars] | @csv |
A Real-World Workflow: Processing API Responses in Shell Scripts
jq integrates cleanly into shell scripts. Here's a pattern for extracting values into shell variables:
response=$(curl -s https://api.example.com/status)
status=$(echo "$response" | jq -r '.status')
version=$(echo "$response" | jq -r '.version')
echo "Service: $status (v$version)"
The -r flag outputs raw strings (without JSON quotes) — essential when assigning to shell variables.
Tips for Getting Unstuck
- Use
jq 'type'to check whether you're dealing with an object, array, string, or number at any point in your filter chain. - Test filters interactively at jqplay.org — paste JSON and iterate on your filter live.
- Add
?after a field access (.foo?) to suppress errors when the field doesn't exist. - Use
--arg name valueto pass shell variables into jq filters safely.
jq has a steeper initial learning curve than piping through Python's json.tool, but the investment pays off quickly. Once you're comfortable with select, map, and object construction, you'll reach for it instinctively whenever JSON is involved.