jq One-Liners Filter and Transform JSON from the CLI Without Writing a Script
Seven practical jq recipes for backend devs and DevOps — covering select, map, to_entries, del, group_by, and real-world API reshaping. Input and output shown for every pattern.
You’re looking at a minified API response in your terminal. You need to extract 3 fields, filter out the nulls, and pipe the result to something else. Writing a Python script for this is 20 lines and 2 minutes you don’t have. jq handles it in one command — if you know the patterns.
Seven recipes below. Each one covers a pattern you’ll hit repeatedly when working with API responses or log files. Input, command, and output shown for every recipe.
Install
brew install jq # macOS
apt-get install jq # Ubuntu/Debian
apk add jq # Alpine (Docker images)
Recipe 1: Extract a Field from Every Object in an Array
The most common thing you’ll do with jq: pull one field from every item in a JSON array.
Input (users.json)
[
{"id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin"},
{"id": 2, "name": "Bob", "email": "bob@example.com", "role": "user"},
{"id": 3, "name": "Carol", "email": "carol@example.com", "role": "user"}
]
jq '.[].name' users.json
# → "Alice"
# "Bob"
# "Carol" (newline-separated stream)
.[] iterates over every element in the array; .name plucks the field. The output is a stream — useful for piping to other commands. If you need a proper JSON array back:
jq 'map(.name)' users.json
# → ["Alice", "Bob", "Carol"]
map(.name) é abreviação de [.[] | .name] — it applies the expression to each item and wraps the results in an array. You’ll use map() a lot.
Recipe 2: Filter with select
Keep only the objects that match a condition; drop the rest.
jq 'map(select(.role == "admin"))' users.json
[
{"id": 1, "name": "Alice", "email": "alice@example.com", "role": "admin"}
]
select(expr) passes the item through only when the expression is truthy — everything else disappears. Wrapping it in map() keeps the output as an array. More examples:
# Numeric comparison
jq 'map(select(.score > 80))' results.json
# Not-null check
jq 'map(select(.error != null))' events.json
# String contains (requires test)
jq 'map(select(.message | test("timeout")))' logs.json
Recipe 3: Reshape Objects with map
Pick only the fields you need and optionally rename them in the same pass.
jq 'map({name: .name, role: .role})' users.json
[
{"name": "Alice", "role": "admin"},
{"name": "Bob", "role": "user"},
{"name": "Carol", "role": "user"}
]
You’re building a new object literal for each item. To rename a key, change the left side of the colon:
jq 'map({username: .name, access_level: .role})' users.json
One shorthand that saves keystrokes: {name} is equivalent to {name: .name}. If you want a field as-is, you don’t have to write the key twice:
jq 'map({id, name, role})' users.json
Recipe 4: Rename Keys with to_entries e from_entries
map is good when you know which keys exist. When you need to transform the keys themselves — add a prefix, convert naming conventions, or rename based on a lookup — to_entries is the right pattern.
Input (config.json)
{"api_url": "https://api.example.com", "timeout": 30, "retry_count": 3}
# Add cfg_ prefix to every key
jq 'to_entries | map(.key = "cfg_" + .key) | from_entries' config.json
{"cfg_api_url": "https://api.example.com", "cfg_timeout": 30, "cfg_retry_count": 3}
to_entries turns {"k": "v"} em [{"key": "k", "value": "v"}]. After you mutate the entries in the resulting array, from_entries reconstructs the object. To rename one specific key without touching the others:
jq 'to_entries | map(if .key == "api_url" then .key = "endpoint" else . end) | from_entries' config.json
Recipe 5: Strip Fields with del
The inverse of reshaping: keep everything and remove a few specific fields. The main use case is stripping sensitive data before logging or before passing a payload to a third-party service.
jq 'map(del(.email))' users.json
[
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "user"},
{"id": 3, "name": "Carol", "role": "user"}
]
Delete multiple fields at once, or delete nested paths:
# Multiple top-level fields at once
jq 'del(.password, .token, .refresh_token)' user.json
# Nested path
jq 'del(.user.internal_id)' response.json
# All fields named "debug" at any depth (recursive descent)
jq 'del(.. | .debug? // empty)' response.json
Recipe 6: Filter, Reshape, and Sort in One Pass
The real payoff is chaining these primitives. Here’s a pattern that shows up constantly when working with paginated API responses: descend into a nested array, filter it, reshape the objects, and sort the result.
Input (repos.json)
{
"total_count": 3,
"items": [
{"id": 1, "name": "repo-alpha", "stargazers_count": 142, "language": "Go", "private": false},
{"id": 2, "name": "repo-beta", "stargazers_count": 89, "language": "Python", "private": true},
{"id": 3, "name": "repo-gamma", "stargazers_count": 310, "language": "Go", "private": false}
]
}
jq '.items
| map(select(.private == false and .language == "Go"))
| sort_by(-.stargazers_count)
| map({name, stars: .stargazers_count})' repos.json
[
{"name": "repo-gamma", "stars": 310},
{"name": "repo-alpha", "stars": 142}
]
The pipeline:
.items— descend into the nested arraymap(select(...))— filter in a single pass;andchains conditionssort_by(-.stargazers_count)— negate the value for descending sort;sort_by(.field)alone gives ascendingmap({name, stars: .stargazers_count})— final reshape;{name}é abreviação de{name: .name}
Recipe 7: Count Log Levels with group_by
Frequency analysis on structured log output — no database needed, no awk gymnastics.
Input (logs.json)
[
{"level": "error", "msg": "connection timeout", "service": "auth"},
{"level": "info", "msg": "request received", "service": "api"},
{"level": "error", "msg": "null pointer exception", "service": "worker"},
{"level": "warn", "msg": "slow query detected", "service": "db"},
{"level": "error", "msg": "rate limit exceeded", "service": "api"}
]
jq 'group_by(.level) | map({level: .[0].level, count: length}) | sort_by(-.count)' logs.json
[
{"level": "error", "count": 3},
{"level": "info", "count": 1},
{"level": "warn", "count": 1}
]
group_by(.level) returns an array of arrays — each sub-array holds all entries that share the same level value. .[0].level grabs the level name from the first item in the group; length counts how many entries are in the group.
Add a per-service breakdown in the same query:
jq 'group_by(.level) | map({
level: .[0].level,
count: length,
services: map(.service) | unique
})' logs.json
Referência Rápida
The patterns above in table form for when you need a quick reminder:
| What you want | jq expression |
|---|---|
| All values of a field | map(.field) |
| Filter matching items | map(select(.field == "val")) |
| Reshape each object | map({newKey: .oldKey}) |
| Rename all keys | to_entries | map(.key = ...) | from_entries |
| Remove fields | map(del(.field1, .field2)) |
| Sort ascending | sort_by(.field) |
| Sort descending | sort_by(-.field) |
| Count by group | group_by(.field) | map({key: .[0].field, count: length}) |
| Get all keys of an object | keys |
| Count items in array | length |
| Unique values of a field | map(.field) | unique |
Before You Start: Tame the Blob
API responses often come back minified or deeply nested. If you’re not sure of the path you need, paste the JSON into the Formatador JSON — it pretty-prints with collapsible nodes so you can spot the path before writing the jq expression. After running a transformation, the Comparação JSON tool is useful for verifying the before/after diff when you’re reshaping or stripping fields and want to confirm nothing unexpected changed.
These seven patterns cover the bulk of JSON wrangling you’ll do at the command line. The real power is chaining them — once you can filter, reshape, and sort, you can process most API responses without touching a script file.
Instale nossas extensões
Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida
恵 O placar chegou!
Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!
Ferramentas essenciais
Ver tudo Novas chegadas
Ver tudoAtualizar: Nosso ferramenta mais recente was added on Mai 23, 2026
