Lightweight command-line JSON processor
Like sed/awk for JSON data. Slice, filter, map, and transform structured data with zero runtime dependencies. Powerful filters meet Unix philosophy.
Essential jq filters at a glance — the building blocks for JSON data transformation.
# Pretty-print
jq '.'
# Object field access
jq '.foo'
jq '.foo.bar'
# Array access
jq '.[0]'
jq '.[-1]' # last
jq '.[2:4]' # slice
# Pipe filters
jq '.foo | .bar'
# Multiple outputs
jq '.foo, .bar'
# Array iteration
jq '.[]'
jq '.[] | .name'
# Transform array
jq 'map(.name)'
# Filter by condition
jq 'map(select(.age > 21))'
# Sort and group
jq 'sort_by(.age)'
jq 'group_by(.dept)'
# If-then-else
jq 'if .status == "ok"
then .data
else empty end'
# Default value
jq '.result // "not found"'
# Build new object from fields
jq '{name: .user, id: .uid}'
# Shorthand when key matches field
jq '{name, age}'
Core filters for navigating and accessing JSON data structures.
.The simplest filter — passes input unchanged. Essential for pretty-printing.
echo '{"name":"Alice","age":30}' | jq '.'
# {
# "name": "Alice",
# "age": 30
# }
.fooAccess object fields using dot notation.
echo '{"name":"Alice","age":30}' | jq '.name'
# "Alice"
echo '{"user":{"name":"Bob","id":123}}' | jq '.user.name'
# "Bob"
.[n]Access array elements by index (0-based, negative for reverse).
echo '[10,20,30,40,50]' | jq '.[2]'
# 30
echo '[10,20,30,40,50]' | jq '.[-1]'
# 50 (last element)
.[start:end]Extract array subsets (end index is exclusive).
echo '[0,1,2,3,4,5]' | jq '.[2:4]'
# [2,3]
echo '[0,1,2,3,4,5]' | jq '.[:3]'
# [0,1,2]
echo '[0,1,2,3,4,5]' | jq '.[3:]'
# [3,4,5]
.[]Iterate over array elements or object values.
echo '[1,2,3]' | jq '.[]'
# 1
# 2
# 3
echo '{"a":1,"b":2}' | jq '.[]'
# 1
# 2
.foo?Access fields without errors if missing — returns null instead.
echo '{"name":"Alice"}' | jq '.age?'
# null
echo '[1,2,3]' | jq '.[10]?'
# null
Understanding and working with JSON data types in jq.
jq supports all JSON types: null, boolean, number, string, array, object
The type function returns the type of a value as a string.
echo 'null' | jq 'type'
# "null"
echo '42' | jq 'type'
# "number"
echo '"hello"' | jq 'type'
# "string"
echo '[1,2,3]' | jq 'type'
# "array"
echo '{"a":1}' | jq 'type'
# "object"
echo 'true' | jq 'type'
# "boolean"
# Select by type
jq 'select(type == "array")'
# Type-specific filters
jq '.[] | arrays'
jq '.[] | objects'
jq '.[] | numbers'
# Check for null
echo 'null' | jq '. == null'
# true
# Filter out nulls
echo '[1, null, 2, null]' | jq '[.[] | select(. != null)]'
# [1,2]
# Provide default for null
echo '{"name": null}' | jq '.name // "unknown"'
# "unknown"
Build new objects from existing data — reshape, extract, and transform.
Create new objects from existing fields.
echo '{"first":"Alice","last":"Smith","age":30}' | \
jq '{name: .first, age: .age}'
# {
# "name": "Alice",
# "age": 30
# }
# Shorthand when key matches field
echo '{"name":"Bob","age":25}' | jq '{name, age}'
# {
# "name": "Bob",
# "age": 25
# }
Use parentheses for dynamic key names.
echo '{"key":"username","value":"alice"}' | \
jq '{(.key): .value}'
# {
# "username": "alice"
# }
# Multiple computed keys
echo '{"k1":"name","v1":"Alice","k2":"age","v2":30}' | \
jq '{(.k1): .v1, (.k2): .v2}'
# {
# "name": "Alice",
# "age": 30
# }
Built-in formatters for encoding and transforming strings.
| Format | Description | Example |
|---|---|---|
@text |
Plain text (default) | jq -r '.name | @text' |
@json |
JSON encode | jq '@json' |
@html |
HTML escape | jq '@html' |
@uri |
URL encode | jq '@uri' |
@base64 |
Base64 encode | jq '@base64' |
@base64d |
Base64 decode | jq '@base64d' |
@sh |
Shell escape | jq '@sh' |
@csv |
CSV format | jq '@csv' |
@tsv |
TSV format | jq '@tsv' |
# Array of objects to CSV
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | \
jq -r '.[] | [.name, .age] | @csv'
# "Alice",30
# "Bob",25
# Add header row
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | \
jq -r '["name","age"], (.[] | [.name, .age]) | @csv'
# "name","age"
# "Alice",30
# "Bob",25
Powerful array manipulation — map, filter, sort, group, and aggregate.
echo '[1,2,3,4,5]' | jq 'map(. * 2)'
# [2,4,6,8,10]
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | \
jq 'map(.name)'
# ["Alice","Bob"]
# Nested map
echo '[[1,2],[3,4]]' | jq 'map(map(. * 2))'
# [[2,4],[6,8]]
echo '[1,2,3,4,5]' | jq '[.[] | select(. > 3)]'
# [4,5]
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | \
jq '[.[] | select(.age >= 30)]'
# [{"name":"Alice","age":30}]
# Multiple conditions
echo '[1,2,3,4,5,6]' | jq '[.[] | select(. > 2 and . < 5)]'
# [3,4]
echo '[[1,2],[3,[4,5]]]' | \
jq 'flatten'
# [1,2,3,4,5]
# Flatten to depth
echo '[[1,[2,[3]]]]' | \
jq 'flatten(1)'
# [1,2,[3]]
echo '[1,2,3,4,5]' | \
jq 'reverse'
# [5,4,3,2,1]
echo '[{"name":"Alice","dept":"eng"},{"name":"Bob","dept":"sales"},{"name":"Carol","dept":"eng"}]' | \
jq 'group_by(.dept)'
# [
# [{"name":"Alice","dept":"eng"},{"name":"Carol","dept":"eng"}],
# [{"name":"Bob","dept":"sales"}]
# ]
# Group and transform
echo '[{"name":"Alice","age":30},{"name":"Bob","age":30},{"name":"Carol","age":25}]' | \
jq 'group_by(.age) | map({age: .[0].age, people: map(.name)})'
# [
# {"age":25,"people":["Carol"]},
# {"age":30,"people":["Alice","Bob"]}
# ]
echo '[{"name":"Carol","age":25},{"name":"Alice","age":30},{"name":"Bob","age":20}]' | \
jq 'sort_by(.age)'
# [
# {"name":"Bob","age":20},
# {"name":"Carol","age":25},
# {"name":"Alice","age":30}
# ]
# Reverse sort
echo '[3,1,4,1,5]' | jq 'sort | reverse'
# [5,4,3,1,1]
# Sort by multiple fields
echo '[{"a":2,"b":1},{"a":1,"b":2},{"a":1,"b":1}]' | \
jq 'sort_by(.a, .b)'
echo '[1,2,2,3,3,3,4]' | \
jq 'unique'
# [1,2,3,4]
# Unique by field
jq 'unique_by(.name)'
echo '[3,1,4,1,5,9,2,6]' | \
jq 'min'
# 1
echo '[3,1,4,1,5,9,2,6]' | \
jq 'max'
# 9
jq 'min_by(.age)'
jq 'max_by(.age)'
echo '[1,2,3,4,5]' | \
jq 'add'
# 15
echo '["hello"," ","world"]' | \
jq 'add'
# "hello world"
# Default for empty
echo '[]' | jq 'add // 0'
# 0
echo '[1,2,3,4,5]' | \
jq 'length'
# 5
echo '{"a":1,"b":2,"c":3}' | \
jq 'length'
# 3
echo '"hello"' | jq 'length'
# 5
echo '["a","b","c"]' | jq 'contains(["b"])'
# true
echo '{"a":1,"b":2}' | jq 'contains({"a":1})'
# true
echo '["b"]' | jq 'inside(["a","b","c"])'
# true
Powerful string manipulation — split, join, match, replace, and format.
echo '"a,b,c,d"' | \
jq 'split(",")'
# ["a","b","c","d"]
echo '["a","b","c"]' | \
jq 'join(",")'
# "a,b,c"
echo '"hello123"' | \
jq 'test("[0-9]+")'
# true
echo '"hello"' | \
jq 'test("^[a-z]+$")'
# true
# Case-insensitive
jq 'test("hello"; "i")'
echo '"The price is $42.50"' | jq 'match("\\$([0-9.]+)")'
# {
# "offset": 13,
# "length": 6,
# "string": "$42.50",
# "captures": [
# {
# "offset": 14,
# "length": 5,
# "string": "42.50",
# "name": null
# }
# ]
# }
# Global match
echo '"abc123def456"' | jq '[match("[0-9]+"; "g")]'
# [
# {"offset":3,"length":3,"string":"123","captures":[]},
# {"offset":9,"length":3,"string":"456","captures":[]}
# ]
echo '"User: alice, ID: 123"' | \
jq 'capture("User: (?\\w+), ID: (?\\d+)")'
# {
# "name": "alice",
# "id": "123"
# }
# Use captured values
echo '"price: $42.50"' | \
jq 'capture("\\$(?[0-9.]+)") | .price | tonumber'
# 42.5
# Replace first occurrence
echo '"hello world"' | jq 'sub("l"; "L")'
# "heLlo world"
# Replace all occurrences
echo '"hello world"' | jq 'gsub("l"; "L")'
# "heLLo worLd"
# Regex replacement
echo '"price: $42.50"' | jq 'gsub("\\$[0-9.]+"; "REDACTED")'
# "price: REDACTED"
# Backreferences
echo '"2024-01-15"' | \
jq 'gsub("(?[0-9]{4})-(?[0-9]{2})-(?[0-9]{2})"; "\\(.d)/\\(.m)/\\(.y)")'
# "15/01/2024"
# Remove prefix
jq 'ltrimstr("https://")'
# Remove suffix
jq 'rtrimstr(".txt")'
jq 'ascii_downcase'
# "hello world"
jq 'ascii_upcase'
# "HELLO WORLD"
jq 'startswith("hello")'
# true
jq 'endswith(".pdf")'
# true
\(expr) Inside Stringsecho '{"name":"Alice","age":30}' | \
jq '"Hello, \(.name)! You are \(.age) years old."'
# "Hello, Alice! You are 30 years old."
# With expressions
echo '{"price":42.5}' | \
jq '"Total: $\(.price * 1.1 | floor)"'
# "Total: $46"
echo '"42"' | jq 'tonumber'
# 42
echo '42' | jq 'tostring'
# "42"
jq 'tonumber | . * 2'
echo '["a","b","c","b","d"]' | \
jq 'indices("b")'
# [1,3]
echo '[1,2,3,2,1]' | \
jq 'indices(2)'
# [1,3]
Logic, conditionals, and control flow for dynamic transformations.
echo '{"age":25}' | \
jq 'if .age >= 18 then "adult" else "minor" end'
# "adult"
# Multiple conditions with elif
echo '42' | \
jq 'if . < 33 then "small" elif . < 66 then "medium" else "large" end'
# "medium"
# Nested conditionals
echo '{"status":"success","data":123}' | \
jq 'if .status == "success" then
(if .data then .data else 0 end)
else null end'
# 123
//echo '{"name":"Alice"}' | jq '.age // 0'
# 0
echo 'null' | jq '. // "default"'
# "default"
echo 'false' | jq '. // "default"'
# "default"
echo '[]' | jq 'add // 0'
# 0
# Chain alternatives
echo '{}' | jq '.a // .b // .c // "none"'
# "none"
# Handle errors gracefully
echo '{"name":"Alice"}' | jq 'try .age catch "not found"'
# "not found"
# Suppress errors with ?
echo '{"name":"Alice"}' | jq '.age?'
# null
echo '"not-a-number"' | jq 'try tonumber catch 0'
# 0
| Operator | Description | Example |
|---|---|---|
== |
Equal to | .a == .b |
!= |
Not equal to | .a != .b |
> |
Greater than | .x > 5 |
>= |
Greater than or equal | .x >= 10 |
< |
Less than | .x < 20 |
<= |
Less than or equal | .x <= 10 |
# and
echo '{"x":10,"y":20}' | jq '.x > 5 and .y < 30'
# true
# or
echo '{"status":"error"}' | jq '.status == "error" or .status == "warning"'
# true
# not
echo '{"active":true}' | jq '.active | not'
# false
# Complex logic
echo '{"age":25,"verified":true}' | jq '.age >= 18 and .verified'
# true
Only false and null are falsy — everything else is truthy. This means 0, "", and [] are all truthy values.
Power tools for complex transformations — reduce, recurse, walk, and more.
echo '[1,2,3,4,5]' | \
jq 'reduce .[] as $item (0; . + $item)'
# 15
echo '[{"name":"Alice","age":30},{"name":"Bob","age":25}]' | \
jq 'reduce .[] as $item ({}; .[$item.name] = $item.age)'
# {
# "Alice": 30,
# "Bob": 25
# }
echo '["a","b","a","c","b","a"]' | \
jq 'reduce .[] as $item ({}; .[$item] = (.[$item] // 0) + 1)'
# {
# "a": 3,
# "b": 2,
# "c": 1
# }
# Generate sequence
echo '1' | jq '[limit(5; recurse(. + 1))]'
# [1,2,3,4,5]
# Traverse nested structure
echo '{"name":"root","children":[{"name":"a","children":[{"name":"b"}]},{"name":"c"}]}' | \
jq '.. | objects | .name'
# "root"
# "a"
# "b"
# "c"
# Increment all numbers
echo '{"a":1,"b":{"c":2,"d":3}}' | \
jq 'walk(if type == "number" then . + 1 else . end)'
# {
# "a": 2,
# "b": {
# "c": 3,
# "d": 4
# }
# }
# Remove null values recursively
echo '{"a":1,"b":null,"c":{"d":2,"e":null}}' | \
jq 'walk(if type == "object" then with_entries(select(.value != null)) else . end)'
# {
# "a": 1,
# "c": {
# "d": 2
# }
# }
# Limit outputs
jq '[limit(3; .[])]'
# [1,2,3]
# First element
jq 'first'
# 1
# Last element
jq 'last'
# 5
# Nth element
jq 'nth(2)'
# 30
# Find next power of 2
echo '50' | \
jq 'until(. >= 50; . * 2)'
# 64
# Countdown
echo '5' | \
jq '[., until(. <= 0; . - 1)]'
# [5,4,3,2,1,0]
# Get environment variable
USER=alice jq -n 'env.USER'
# "alice"
# Use in filter
PORT=8080 jq -n '{
host: "localhost",
port: (env.PORT | tonumber)
}'
# Read next input
jq -n 'input'
# Read all inputs
jq -n '[inputs]'
# Combine inputs
jq -s 'add'
Master filter composition — pipe, comma, and control flow operators.
|echo '{"user":{"name":"Alice","age":30}}' | jq '.user | .name'
# "Alice"
# Chain multiple operations
echo '[3,1,4,1,5,9]' | jq 'sort | reverse | .[0:3]'
# [9,5,4]
,echo '{"name":"Alice","age":30}' | jq '.name, .age'
# "Alice"
# 30
# Collect with array
echo '{"name":"Alice","age":30}' | jq '[.name, .age]'
# ["Alice",30]
# Multiple transformations
echo '{"x":10}' | jq '.x, .x * 2, .x * 3'
# 10
# 20
# 30
echo '[1,2,3]' | jq '(.[] | . * 2)'
# 2
# 4
# 6
# Vs without parentheses
echo '[1,2,3]' | jq '[.[] | . * 2]'
# [2,4,6]
The semicolon ; is used to separate function parameters, not for sequencing operations.
# limit(n; expr)
jq '[limit(5; range(10))]'
# until(condition; update)
jq 'until(. > 100; . * 2)'
Real-world use cases and common workflows for JSON data processing.
# Extract specific fields from GitHub API
curl -s https://api.github.com/repos/jqlang/jq | \
jq '{name, stars: .stargazers_count, issues: .open_issues_count}'
# Get list of user repos
curl -s https://api.github.com/users/octocat/repos | \
jq '[.[] | {name, url: .html_url, stars: .stargazers_count}]'
# Filter and sort
curl -s https://api.github.com/users/octocat/repos | \
jq '[.[] | select(.stargazers_count > 10)] | sort_by(-.stargazers_count) | .[0:5]'
# Filter errors
cat app.log | \
jq 'select(.level == "error")'
# Count by type
cat app.log | \
jq -s 'group_by(.error_type) |
map({
type: .[0].error_type,
count: length
})'
cat app.log | \
jq 'select(
.timestamp >= "2024-01-01"
and
.timestamp < "2024-02-01"
)'
cat metrics.log | jq -s '{
total: length,
avg_duration: (
map(.duration) | add / length
),
max_duration: (
map(.duration) | max
),
min_duration: (
map(.duration) | min
)
}'
# Update config value
jq '.database.port = 5432' \
config.json > config.new.json
# Merge configs
jq -s '.[0] * .[1]' \
config1.json config2.json \
> merged.json
# Remove sensitive data
jq 'del(.secrets, .api_keys)' \
config.json
# Extract environment
jq '.environments.production' \
config.json
echo '[{"id":"a","value":1},{"id":"b","value":2}]' | \
jq 'map({(.id): .value}) | add'
# {"a":1,"b":2}
echo '{"a":1,"b":2,"c":3}' | \
jq 'to_entries | map({key, value})'
# [
# {"key":"a","value":1},
# {"key":"b","value":2},
# {"key":"c","value":3}
# ]
echo '{"users":[{"name":"Alice","addresses":[{"city":"NYC"},{"city":"LA"}]}]}' | \
jq '[.users[] | {name, city: .addresses[].city}]'
# [
# {"name":"Alice","city":"NYC"},
# {"name":"Alice","city":"LA"}
# ]
echo '[{"dept":"eng","salary":100},{"dept":"sales","salary":80},{"dept":"eng","salary":120}]' | \
jq 'group_by(.dept) |
map({
dept: .[0].dept,
total: (map(.salary) | add),
count: length
})'
# [
# {"dept":"eng","total":220,"count":2},
# {"dept":"sales","total":80,"count":1}
# ]
jq 'select(.name and .email and .age)' \
users.json
# Remove invalid entries
jq '[.[] | select(.age >= 0 and .age <= 150)]' \
users.json
# Validate email format
jq '[.[] | select(.email | test("@.*\\..*"))]' \
users.json
# Find inconsistencies
jq '[.[] | select(.created_at > .updated_at)]' \
records.json
Expert workflows, performance optimization, and advanced techniques.
# Pretty-print API response
curl -s https://api.github.com/users/octocat | jq '.'
# Extract single field
curl -s https://api.github.com/users/octocat | jq -r '.name'
# Store token, use in request
TOKEN=$(jq -r '.token' auth.json)
curl -H "Authorization: Bearer $TOKEN" https://api.example.com/data | jq '.'
# Pipe to other commands
curl -s https://api.github.com/users/octocat/repos | \
jq -r '.[].clone_url' | xargs -I {} git clone {}
#!/bin/bash
# Read config
DB_HOST=$(jq -r '.database.host' config.json)
DB_PORT=$(jq -r '.database.port' config.json)
# Process array
jq -c '.[]' data.json | while read -r item; do
name=$(echo "$item" | jq -r '.name')
echo "Processing: $name"
done
# Build JSON
output=$(jq -n \
--arg host "$HOSTNAME" \
--arg time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{hostname: $host, timestamp: $time, status: "ok"}')
# Validate JSON
if jq empty file.json 2>/dev/null; then
echo "Valid JSON"
else
echo "Invalid JSON"
fi
| Option | Description | Example |
|---|---|---|
-r |
Raw output (no JSON quotes) | jq -r '.name' |
-c |
Compact output (no pretty-print) | jq -c '.' |
-s |
Slurp (read entire input into array) | jq -s 'add' |
-n |
Null input (don't read input) | jq -n '{hello: "world"}' |
-e |
Exit status based on output | jq -e '.error' |
-f |
Read filter from file | jq -f filter.jq data.json |
--arg |
Pass string variable | jq --arg name "Alice" '{name: $name}' |
--argjson |
Pass JSON variable | jq --argjson age 30 '{age: $age}' |
--slurpfile |
Read JSON file into variable | jq --slurpfile cfg config.json |
--stream |
Parse in streaming mode (large files) | jq --stream '.' large.json |
# Use --stream for large files
jq --stream 'select(.[0][0] == "target")' \
huge.json
# Use limit() to stop early
jq 'limit(10; .[] | select(.age > 30))' \
users.json
# Single pass vs multiple
# BAD: Multiple passes
jq '.[] | select(.age > 30)' | \
jq 'map(.name)'
# GOOD: Single pass
jq '[.[] | select(.age > 30) | .name]'
# Compact output (faster, less bandwidth)
jq -c '.' data.json
# Read multiple files efficiently
jq -s 'add' \
file1.json \
file2.json \
file3.json
# Use --tab for TSV
jq -r '.[] | [.name, .age] | @tsv' \
users.json
# Print intermediate values with debug
echo '{"a":1,"b":2}' | jq '.a | debug | . * 2'
# ["DEBUG:",1]
# 2
# Use stderr for logging
echo '{"a":1}' | jq '.a | debug("value is:") | . * 2'
# Check types
echo '{"data":"123"}' | jq '.data | type, tonumber | type'
# "string"
# "number"
# Use $__loc__ for location info
echo '{"a":{"b":1}}' | jq '.a.b | $__loc__'
jq '.name' outputs "Alice" (JSON string), use -r for raw outputfalse and null are falsy — "", 0, and [] are truthy.[] produces multiple outputs, use [.[]] to wrap in array{(.key): "value"} not {.key: "value"}test("\\d+")5 / 2 returns 2.5, use floor for integers.a?.b?.c? to safely traverse nullable pathsjq for YAML — same syntax, different format.
yq eval '.servers[0].host' \
config.yaml
# Convert YAML to JSON
yq eval -o=json '.' \
config.yaml | jq '.'
Pure Go jq — faster startup, library usage.
gojq '.' data.json
# Same syntax as jq
# Can be used as library
Rust-based jq — significantly faster for large files.
jaq '.' data.json
# Compatible with most jq
# Faster for large datasets
Interactive JSON viewer with autocomplete.
fx data.json
# Interactive browsing
# JavaScript-based filtering
Vim-like JSON viewer with tree expansion.
jless data.json
# Vim keybindings
# Expandable tree view
Use jqplay.org — the official interactive playground for testing jq filters with real-time results, shareable URLs, and syntax highlighting.
Alternative: DevToolsDaily jq Playground
# macOS
brew install jq
# Ubuntu/Debian
sudo apt-get install jq
# Fedora
sudo dnf install jq
# Windows (Chocolatey)
choco install jq
git clone \
https://github.com/jqlang/jq.git
cd jq
autoreconf -fi
./configure
make
sudo make install