jq JSON Processing
JSON parsing, filtering, and transformation with jq.
Basic Navigation
# Pretty print
cat file.json | jq .
echo '{"a":1}' | jq .
# Access object keys
jq '.key' file.json # Top-level key
jq '.nested.key' file.json # Nested key
jq '.a.b.c' file.json # Deep nesting
# Handle null/missing keys
jq '.missing' file.json # Returns null
jq '.missing // "default"' file.json # Default value
# Keys with special characters
jq '.["key-with-dash"]' file.json
jq '.["key with space"]' file.json
jq '."key-with-dash"' file.json # Alternative
# Multiple keys
jq '.key1, .key2' file.json # Both values (separate outputs)
jq '{a: .key1, b: .key2}' file.json # Combined into object
# Raw output (no quotes for strings)
jq -r '.name' file.json
# Compact output (no pretty print)
jq -c '.' file.json
# Infrastructure: Get ISE session user
netapi ise mnt sessions --format json | jq -r '.[0].user_name'
# Infrastructure: Get Vault token info
vault token lookup -format=json | jq -r '.data.display_name'
Array Operations
# Access array elements
jq '.[0]' file.json # First element
jq '.[-1]' file.json # Last element
jq '.[2:5]' file.json # Slice (elements 2,3,4)
jq '.[:3]' file.json # First 3 elements
jq '.[-3:]' file.json # Last 3 elements
# Iterate all elements
jq '.[]' file.json # Each element (separate outputs)
jq '.[] | .name' file.json # Each element's name
# Array length
jq 'length' file.json # Array length
jq '.items | length' file.json # Nested array length
# Array operations
jq 'first' file.json # First element (same as .[0])
jq 'last' file.json # Last element
jq 'reverse' file.json # Reverse array
jq 'unique' file.json # Remove duplicates
jq 'sort' file.json # Sort (strings/numbers)
jq 'sort_by(.name)' file.json # Sort by field
jq 'group_by(.type)' file.json # Group by field
# Add element
jq '. + ["new"]' file.json # Append
jq '["first"] + .' file.json # Prepend
# Flatten nested arrays
jq 'flatten' file.json # Flatten all levels
jq 'flatten(1)' file.json # Flatten 1 level
# Infrastructure: Get all ISE session MACs
netapi ise mnt sessions --format json | jq -r '.[].calling_station_id'
# Infrastructure: Get k8s pod names
kubectl get pods -o json | jq -r '.items[].metadata.name'
# Infrastructure: Sort ISE sessions by user
netapi ise mnt sessions --format json | jq 'sort_by(.user_name)'
Filtering with select
# Basic filter
jq '.[] | select(.status == "active")' file.json
jq '.[] | select(.value > 10)' file.json
jq '.[] | select(.name != null)' file.json
# String operations
jq '.[] | select(.name | startswith("vault"))' file.json
jq '.[] | select(.name | endswith(".dev"))' file.json
jq '.[] | select(.name | contains("prod"))' file.json
jq '.[] | select(.name | test("^vault-[0-9]+$"))' file.json # Regex
# Negation
jq '.[] | select(.status != "failed")' file.json
jq '.[] | select(.name | contains("test") | not)' file.json
# Multiple conditions (AND)
jq '.[] | select(.status == "active" and .value > 10)' file.json
# Multiple conditions (OR)
jq '.[] | select(.status == "active" or .status == "pending")' file.json
# Check if field exists
jq '.[] | select(has("email"))' file.json
jq '.[] | select(.email != null)' file.json
# Type checking
jq '.[] | select(.value | type == "number")' file.json
jq '.[] | select(.data | type == "array")' file.json
# In array (membership)
jq '.[] | select(.role | IN("admin", "manager"))' file.json
# Infrastructure: Failed ISE authentications
netapi ise mnt sessions --format json | jq '.[] | select(.authentication_status == "FAILED")'
# Infrastructure: Pods not running
kubectl get pods -o json | jq '.items[] | select(.status.phase != "Running") | .metadata.name'
# Infrastructure: High CPU pods
kubectl top pods -o json | jq '.items[] | select(.containers[0].usage.cpu | rtrimstr("m") | tonumber > 500)'
# Infrastructure: ISE sessions from specific switch
netapi ise mnt sessions --format json | jq '.[] | select(.nas_ip_address == "10.50.1.11")'
Object Construction
# Create new object
jq '{name: .title, id: .uuid}' file.json
# Rename keys
jq '{new_name: .old_name}' file.json
# Preserve original field name (shorthand)
jq '{name, id, status}' file.json # Same as {name: .name, id: .id, status: .status}
# Add computed fields
jq '{name, full_name: (.first + " " + .last)}' file.json
# Conditional fields
jq '{name, status: (if .active then "up" else "down" end)}' file.json
# Nested object creation
jq '{
meta: {name: .name, id: .id},
data: {value: .value, count: .items | length}
}' file.json
# From array to object
jq '[.[] | {key: .name, value: .count}] | from_entries' file.json
jq 'map({(.name): .value}) | add' file.json
# Extract multiple into array of objects
jq '[.[] | {name, status}]' file.json
# Infrastructure: ISE session summary
netapi ise mnt sessions --format json | jq '[.[] | {
mac: .calling_station_id,
user: .user_name,
switch: .nas_ip_address,
status: .authentication_status
}]'
# Infrastructure: k8s pod summary
kubectl get pods -o json | jq '[.items[] | {
name: .metadata.name,
namespace: .metadata.namespace,
status: .status.phase,
ip: .status.podIP
}]'
# Infrastructure: Vault secret metadata
vault kv metadata list -format=json secret/ | jq '[.data.keys[] | {path: ., full: ("secret/" + .)}]'
Map, Reduce, and Transformations
# map - apply to each array element
jq 'map(.name)' file.json # Array of names
jq 'map(. + 1)' file.json # Increment each number
jq 'map(select(.active))' file.json # Filter, keep array structure
# map_values - apply to each object value
jq 'map_values(. * 2)' file.json # Double each value
# reduce - aggregate values
jq 'reduce .[] as $x (0; . + $x)' file.json # Sum
jq 'reduce .[] as $x (0; . + $x.value)' file.json # Sum field
jq 'reduce .[] as $x (""; . + $x.name + ",")' file.json # Concatenate
# group_by + map for aggregation
jq 'group_by(.type) | map({type: .[0].type, count: length})' file.json
# Pivot table style
jq 'group_by(.category) | map({
category: .[0].category,
total: map(.value) | add,
count: length,
avg: (map(.value) | add / length)
})' file.json
# min/max
jq 'min' file.json # Min of array
jq 'max_by(.value)' file.json # Element with max value
jq '[.[] | .value] | min' file.json # Min value from objects
# add - sum/concatenate
jq '[.[] | .count] | add' file.json # Sum counts
jq '[.[] | .name] | join(", ")' file.json # Join names
# Infrastructure: Total ISE sessions by status
netapi ise mnt sessions --format json | jq 'group_by(.authentication_status) | map({
status: .[0].authentication_status,
count: length
})'
# Infrastructure: k8s resource summary by namespace
kubectl get pods -o json | jq '.items | group_by(.metadata.namespace) | map({
namespace: .[0].metadata.namespace,
pods: length,
running: map(select(.status.phase == "Running")) | length
})'
String Operations
# Concatenation
jq '.first + " " + .last' file.json
# Interpolation
jq '"User: \(.name), ID: \(.id)"' file.json
# Split and join
jq '.name | split("-")' file.json # "vault-01" -> ["vault", "01"]
jq '.items | join(", ")' file.json # ["a", "b"] -> "a, b"
# Case conversion
jq '.name | ascii_downcase' file.json
jq '.name | ascii_upcase' file.json
# Trim whitespace
jq '.name | ltrimstr(" ")' file.json # Left trim
jq '.name | rtrimstr(" ")' file.json # Right trim
jq '.name | gsub("^\\s+|\\s+$"; "")' file.json # Both sides
# Substring
jq '.name | .[0:5]' file.json # First 5 chars
jq '.name | .[-3:]' file.json # Last 3 chars
# Replace
jq '.name | gsub("-"; "_")' file.json # Replace all
jq '.name | sub("-"; "_")' file.json # Replace first
# Regex match
jq '.name | test("vault-[0-9]+")' file.json # Boolean
jq '.name | match("vault-([0-9]+)").captures[0].string' file.json # Capture group
# Parse numbers from strings
jq '.cpu | rtrimstr("m") | tonumber' file.json # "500m" -> 500
jq '.mem | rtrimstr("Mi") | tonumber' file.json # "256Mi" -> 256
# Format numbers as strings
jq '.value | tostring' file.json
jq '"Value: \(.value)"' file.json
# Infrastructure: Extract hostname from FQDN
netapi ise mnt sessions --format json | jq -r '.[].nas_identifier | split(".")[0]'
# Infrastructure: Normalize MAC addresses
netapi ise mnt sessions --format json | jq -r '.[].calling_station_id | gsub(":"; "-") | ascii_downcase'
Conditionals
# if-then-else
jq 'if .value > 10 then "high" else "low" end' file.json
# Nested conditionals
jq 'if .value > 100 then "critical"
elif .value > 50 then "warning"
else "ok" end' file.json
# Conditional in object construction
jq '{
name,
level: (if .value > 100 then "high" elif .value > 50 then "medium" else "low" end)
}' file.json
# Empty/null handling
jq '.items // []' file.json # Default to empty array
jq '.name // "unknown"' file.json # Default string
jq 'if .name then .name else "N/A" end' file.json
# Alternative operator (or)
jq '.primary // .secondary // "fallback"' file.json
# Error suppression
jq '.items[]? | .name' file.json # ? suppresses errors
# try-catch
jq 'try .value catch "error"' file.json
# Infrastructure: ISE status indicator
netapi ise mnt sessions --format json | jq -r '.[] |
if .authentication_status == "AUTHENTICATED" then "✓"
elif .authentication_status == "FAILED" then "✗"
else "?" end + " " + .calling_station_id'
# Infrastructure: k8s pod health
kubectl get pods -o json | jq '.items[] | {
name: .metadata.name,
healthy: (if .status.phase == "Running" and (.status.containerStatuses // []) | all(.ready) then true else false end)
}'
Advanced Patterns
# Path expressions
jq 'paths' file.json # All paths
jq 'path(.a.b)' file.json # Path to specific key
jq 'getpath(["a", "b"])' file.json # Get value at path
# Recursive descent
jq '.. | numbers' file.json # All numbers anywhere
jq '.. | strings | select(test("error"))' file.json # All error strings
# Walk and transform
jq 'walk(if type == "string" then ascii_downcase else . end)' file.json
# Update in place
jq '.items[0].status = "updated"' file.json
jq '.items[] |= . + {processed: true}' file.json
jq '.count += 1' file.json
# Delete keys
jq 'del(.unwanted)' file.json
jq 'del(.items[].internal)' file.json
# Merge objects
jq '. + {new_field: "value"}' file.json
jq '. * {nested: {deep: "update"}}' file.json # Deep merge
# Env variables
jq --arg name "$USER" '{user: $name}' file.json
jq --argjson count 5 '{count: $count}' file.json
# From file
jq --slurpfile config config.json '. + $config[0]' file.json
# Multiple inputs
jq -s '.[0] + .[1]' file1.json file2.json # Merge two files
jq -s 'add' file1.json file2.json # Combine arrays
# Stream processing (for huge files)
jq -c --stream 'select(.[0][0] == "items")' huge.json
# Infrastructure: Update Vault policy
vault policy read -format=json default | jq '.rules |= . + "\npath \"secret/*\" { capabilities = [\"read\"] }"'
# Infrastructure: Merge k8s configs
jq -s '.[0] * .[1]' base.json overlay.json
Output Formats
# Raw strings (no quotes)
jq -r '.name' file.json
# Compact (single line)
jq -c '.' file.json
# Tab-separated
jq -r '[.name, .value, .status] | @tsv' file.json
# CSV
jq -r '[.name, .value, .status] | @csv' file.json
# With headers
jq -r '["NAME", "VALUE", "STATUS"], (.[] | [.name, .value, .status]) | @csv' file.json
# URI encoding
jq -r '.query | @uri' file.json
# Base64
jq -r '.data | @base64' file.json
jq -r '.encoded | @base64d' file.json # Decode
# HTML encoding
jq -r '.text | @html' file.json
# JSON text (for nested strings)
jq -r '.config | @json' file.json
# Shell-safe
jq -r '.args | @sh' file.json # Quote for shell
# Infrastructure: ISE sessions as TSV
netapi ise mnt sessions --format json | jq -r '.[] | [.calling_station_id, .user_name, .authentication_status] | @tsv'
# Infrastructure: Export to CSV for Excel
kubectl get pods -o json | jq -r '
["NAMESPACE","NAME","STATUS","IP"],
(.items[] | [.metadata.namespace, .metadata.name, .status.phase, .status.podIP // "N/A"])
| @csv' > pods.csv
Infrastructure Patterns
# ISE: Failed auth analysis
netapi ise mnt sessions --format json | jq '
[.[] | select(.authentication_status == "FAILED")] |
group_by(.failure_reason) |
map({reason: .[0].failure_reason, count: length, users: [.[].user_name] | unique}) |
sort_by(-.count)'
# ISE: Sessions per switch
netapi ise mnt sessions --format json | jq '
group_by(.nas_ip_address) |
map({
switch: .[0].nas_ip_address,
total: length,
authenticated: map(select(.authentication_status == "AUTHENTICATED")) | length,
failed: map(select(.authentication_status == "FAILED")) | length
}) |
sort_by(-.total)'
# ISE: Policy set hit counts
netapi ise openapi GET '/api/v1/policy/network-access/policy-set' | jq '
.response | map({name, hitCount: .hitCounts.hitCount}) | sort_by(-.hitCount)'
# k8s: Pod resource usage
kubectl top pods -o json | jq '.items | map({
name: .metadata.name,
cpu: (.containers[0].usage.cpu | rtrimstr("m") | tonumber),
memory: (.containers[0].usage.memory | rtrimstr("Mi") | tonumber)
}) | sort_by(-.cpu) | .[0:10]'
# k8s: Deployments not at desired replicas
kubectl get deployments -o json | jq '.items | map(select(.status.replicas != .status.readyReplicas)) | map({
name: .metadata.name,
desired: .spec.replicas,
ready: .status.readyReplicas
})'
# Vault: List all secrets recursively
vault kv list -format=json secret/ | jq -r '.data.keys[]' | while read key; do
if [[ "$key" == */ ]]; then
vault kv list -format=json "secret/$key" | jq -r --arg p "$key" '.data.keys[] | "secret/\($p)\(.)"'
else
echo "secret/$key"
fi
done
# Vault: Certificate expiry from PKI
vault list -format=json pki_int/certs | jq -r '.data.keys[]' | head -5 | while read serial; do
vault read -format=json "pki_int/cert/$serial" | jq -r '{
serial: .data.serial_number,
cn: .data.certificate | @base64d | capture("CN=(?<cn>[^,]+)").cn,
expiry: .data.expiration
}'
done
# Wazuh: Agent status
netapi wazuh agents | jq '.data.affected_items | map({
name,
status,
ip,
last_keepalive
}) | sort_by(.status)'
# API response pagination (combine pages)
for page in 1 2 3; do
curl -s "https://api.example.com/items?page=$page"
done | jq -s '[.[].items[]] | flatten'
# Deep diff between two JSON files
diff <(jq -S '.' file1.json) <(jq -S '.' file2.json)
# Find differences in arrays
jq -s '(.[0] - .[1]) as $removed | (.[1] - .[0]) as $added | {removed: $removed, added: $added}' old.json new.json
jq Gotchas
# WRONG: Forgetting -r for strings
name=$(echo '{"name":"vault"}' | jq '.name')
echo "$name" # "vault" (with quotes!)
# CORRECT: Use -r for raw strings
name=$(echo '{"name":"vault"}' | jq -r '.name')
echo "$name" # vault
# WRONG: Not handling null
echo '{"a": null}' | jq '.a.b' # null, not error
echo '{}' | jq '.a.b' # null, not error
# CORRECT: Use // for defaults
echo '{}' | jq '.a.b // "default"' # "default"
# WRONG: Piping when you mean array
jq '.[] | select(.x > 0)' file.json # Separate outputs
jq '[.[] | select(.x > 0)]' file.json # Single array output
# WRONG: Arithmetic on strings
echo '{"cpu": "500m"}' | jq '.cpu + 100' # Error!
# CORRECT: Parse first
echo '{"cpu": "500m"}' | jq '.cpu | rtrimstr("m") | tonumber | . + 100'
# WRONG: Comparing different types
echo '{"val": "10"}' | jq '.val > 5' # false! String vs number
# CORRECT: Convert types
echo '{"val": "10"}' | jq '.val | tonumber > 5' # true
# WRONG: Empty input
echo "" | jq '.' # parse error
# CORRECT: Handle empty
echo "" | jq -e '.' 2>/dev/null || echo "No JSON"
# WRONG: Multiple JSON objects (not in array)
echo '{"a":1}{"b":2}' | jq '.' # Only processes first
# CORRECT: Use -s to slurp
echo '{"a":1}{"b":2}' | jq -s '.' # [{a:1},{b:2}]
# WRONG: Shell variable in single quotes
name="vault"
jq '.[$name]' file.json # Error!
# CORRECT: Use --arg
jq --arg n "$name" '.[$n]' file.json
# WRONG: Expecting map to modify in place
jq '.items | map(.x = 1)' file.json # Only items returned
# CORRECT: Use |=
jq '.items |= map(. + {x: 1})' file.json # Full object returned
# WRONG: Deep merge with +
echo '{"a":{"b":1}}' | jq '. + {"a":{"c":2}}' # {"a":{"c":2}} - overwrites!
# CORRECT: Use * for deep merge
echo '{"a":{"b":1}}' | jq '. * {"a":{"c":2}}' # {"a":{"b":1,"c":2}}