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
lengthCount array items or string chars.[] | length
keysGet object keys as array. | keys
has("field")Check if key existsselect(has("email"))
to_entriesObject → array of {key, value}. | to_entries
sort_by(.field)Sort array by field valuesort_by(.name)
group_by(.field)Group array by fieldgroup_by(.language)
unique_by(.field)Deduplicate by fieldunique_by(.id)
@csv / @tsvFormat 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 value to 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.