If you work with APIs, config files, or logs, you deal with JSON. And if you deal with JSON, you’ve probably found yourself squinting at a wall of curly braces in your terminal, trying to find one specific value buried three levels deep.

That’s where jq comes in — a lightweight command-line JSON processor that lets you slice, filter, and reshape JSON with a few keystrokes. No Python script, no opening a browser. Just pipe your data through jq and get exactly what you need.

Abstract digital network visualization representing JSON data processing on the command line
Image: Tm via Wikimedia Commons (CC BY 4.0)

I started using jq about four years ago when I was debugging a microservice that returned 4,000-line JSON responses. Grepping for field names was a mess — sometimes I’d match a value instead of a key, sometimes nested objects would throw me off entirely. A colleague saw me struggling and said two words: “try jq.” It changed how I work with data on the command line.

In this guide, I’ll walk you through everything from basic field extraction to chained filters and real-world API parsing. Every command here is tested and ready to use.

What You’ll Need

jq is available in every major package manager:

# Ubuntu / Debian
sudo apt install jq

# macOS
brew install jq

# Fedora / RHEL
sudo dnf install jq

Verify it’s installed:

jq --version
# jq-1.7.1 (or newer)

That’s it. No dependencies, no runtime — just a single binary that weighs less than 2MB.

Step 1: Extracting Fields — Your First jq Command

The simplest jq filter is a dot followed by a field name. Given a JSON object, .fieldname extracts that field’s value.

echo '{"name":"Alice","age":30,"city":"Manila"}' | jq '.name'
# "Alice"

echo '{"name":"Alice","age":30,"city":"Manila"}' | jq '.age'
# 30

Notice how jq preserves the type — strings get quotes, numbers don’t. If you want raw output without quotes, use the -r flag:

echo '{"name":"Alice","age":30}' | jq -r '.name'
# Alice

Nested fields work the same way, chained with dots:

echo '{"user":{"name":"Alice","address":{"city":"Manila"}}}' | jq '.user.address.city'
# "Manila"

This is already more useful than grep. You’re pulling exactly the value you want, not a line that happens to contain the word “city.”

Step 2: Building New Objects

Extracting fields is useful, but jq really shines when you reshape data. You can construct entirely new objects by wrapping fields in curly braces:

echo '{"name":"Alice","age":30,"email":"[email protected]","ssn":"123-45-6789"}' | jq '{full_name: .name, years_old: .age}'
# {
#   "full_name": "Alice",
#   "years_old": 30
# }

This is like a SQL SELECT — you pick the columns you want and rename them. I use this constantly when pulling data from APIs that return way more fields than I need.

Step 3: Working with Arrays

JSON arrays are everywhere — API responses, config files, log streams. jq handles them with the [] operator:

echo '{"languages":["Python","JavaScript","Rust","Go"]}' | jq '.languages[]'
# "Python"
# "JavaScript"
# "Rust"
# "Go"

The [] after a field name “explodes” the array so each element gets its own line. You can also access elements by index:

echo '{"languages":["Python","JavaScript","Rust","Go"]}' | jq '.languages[0]'
# "Python"

echo '{"languages":["Python","JavaScript","Rust","Go"]}' | jq '.languages[-1]'
# "Go"

Negative indices count from the end, just like Python.

For arrays of objects, combine [] with field access:

echo '[{"name":"Alice","age":30},{"name":"Bob","age":25},{"name":"Carol","age":35}]' | jq '.[].name'
# "Alice"
# "Bob"
# "Carol"

Step 4: Filtering with select()

This is where jq stops being a pretty-printer and becomes a query tool. select() keeps only the elements that match a condition:

echo '[{"name":"Alice","age":30},{"name":"Bob","age":25},{"name":"Carol","age":35}]' | jq '.[] | select(.age > 28) | .name'
# "Alice"
# "Carol"

Let’s break that down: .[] iterates over the array, the pipe | sends each element through select(.age > 28) which discards anyone 28 or younger, and the final pipe extracts just the name.

You can chain multiple conditions:

jq '.[] | select(.age > 25 and .age < 35) | .name'

Or filter by string values:

jq '.[] | select(.name | startswith("A")) | .name'

Step 5: Transforming Arrays with map()

map() applies a transformation to every element in an array and returns a new array:

echo '[{"name":"Alice","age":30},{"name":"Bob","age":25},{"name":"Carol","age":35}]' | jq 'map({name: .name, senior: (.age > 28)})'
# [
#   {"name":"Alice","senior":true},
#   {"name":"Bob","senior":false},
#   {"name":"Carol","senior":true}
# ]

I use map() more than any other jq function. It's the fastest way to reshape an entire dataset — strip out sensitive fields, add computed properties, or flatten nested structures.

Step 6: Counting and Aggregating

Need to know how many items match a filter? Wrap the result in brackets and use length:

echo '[{"name":"Alice","age":30},{"name":"Bob","age":25},{"name":"Carol","age":35}]' | jq '[.[] | select(.age > 28)] | length'
# 2

For numeric aggregation, jq has add, max, min, and more:

echo '[10, 25, 35, 50]' | jq 'add'
# 120

echo '[10, 25, 35, 50]' | jq 'max'
# 50

Step 7: Sorting and Deduplication

echo '[3,1,4,1,5,9,2,6,5,3,5]' | jq 'sort'
# [1,1,2,3,3,4,5,5,5,6,9]

echo '[3,1,4,1,5,9,2,6,5,3,5]' | jq 'sort | unique'
# [1,2,3,4,5,6,9]

For arrays of objects, sort by a specific field:

jq 'sort_by(.age)' people.json

Step 8: String Interpolation

jq has its own string formatting syntax using \(expression) inside double-quoted strings:

echo '{"results":[{"name":"bulbasaur","types":[{"type":{"name":"grass"}},{"type":{"name":"poison"}}]},{"name":"charmander","types":[{"type":{"name":"fire"}}]}]}' | jq '.results[] | "\(.name) is a \(.types | map(.type.name) | join("/")) type"'
# "bulbasaur is a grass/poison type"
# "charmander is a fire type"

This is genuinely powerful — you can build human-readable summaries from deeply nested JSON in a single pipeline.

Real-World Example: Parsing a REST API Response

Let's pull real data. Here's how you'd fetch and filter Pokémon from the free PokéAPI:

curl -s "https://pokeapi.co/api/v2/pokemon?limit=10" | jq '.results[] | .name'
# "bulbasaur"
# "ivysaur"
# "venusaur"
# ...

Or get the count of Pokémon whose names start with "c":

curl -s "https://pokeapi.co/api/v2/pokemon?limit=50" | jq '[.results[] | select(.name | startswith("c"))] | length'
# 12

This pattern — curl something, pipe through jq, get exactly what you need — is one of those workflows that, once you start using it, you can't go back. If you've been building shell scripts around APIs (like when setting up reverse proxies that talk to multiple backends), jq eliminates half the parsing code.

Common Pitfalls and How to Fix Them

"jq: error: Cannot index array with string"

You're treating an array like an object. Add [] before the dot:

# Wrong — trying to access .name on an array
jq '.name' array.json

# Right — iterate the array first
jq '.[].name' array.json

"jq: error: null has no keys"

The field you're accessing doesn't exist. Use the ? operator to make field access optional:

jq '.optional_field?' data.json

Quotes in the output

Always use -r (raw output) when piping jq results to other commands or writing to files:

# Quotes get passed to grep
jq '.name' data.json | grep Alice  # won't match if output is "Alice"

# Raw output works as expected
jq -r '.name' data.json | grep Alice  # works

Putting It All Together

jq's real strength is chaining. Here's a pipeline that takes a GitHub API response and produces a clean summary:

curl -s "https://api.github.com/repos/jqlang/jq/releases/latest" | jq '{version: .tag_name, published: .published_at, assets: [.assets[] | {name: .name, size: .size}]}'

That single command pulls the latest release version, date, and a clean list of download files — all from a deeply nested API response. No Python, no JavaScript, no temporary files.

This is why jq has earned its place in my terminal toolkit alongside tmux for session management and git bisect for debugging. It's one of those tools that does one thing — process JSON — and does it so well that you reach for it constantly.

If you're building a search engine or document index (like the SQLite FTS5 approach I covered recently), jq becomes an essential part of your data pipeline — cleaning, reshaping, and extracting exactly the fields you need before they hit your database.

And if you're managing servers or monitoring infrastructure, you'll find yourself piping API responses through jq filters constantly. The bash scripting patterns that process structured data become ten times cleaner when jq handles the JSON parsing.

Bottom Line

jq isn't flashy. It won't make headlines or get a venture capital round. But it solves a problem every developer faces — making sense of JSON data quickly, without ceremony — and it does it with a clean, consistent syntax that rewards the time you invest in learning it.

Start with .fieldname and .[]. Add select() when you need filtering. Graduate to map() when you're reshaping data. Before long, you'll wonder how you ever worked with JSON without it.

Have a favorite jq filter or a JSON parsing war story? Drop a comment — I'd love to hear how you're using it.

Filed under Tech & Gadgets
Last Update: June 16, 2026 by Felix AlterEgo
0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Newest
Oldest Most Voted