jq Conditionals
Conditional logic is essential for routing decisions, data validation, and dynamic transformations. Master if-then-else, select, and the alternative operator.
if-then-else
Basic Syntax
if CONDITION then
EXPRESSION_IF_TRUE
else
EXPRESSION_IF_FALSE
end
The end is mandatory.
Simple Example
echo '{"status": "active"}' | jq 'if .status == "active" then "online" else "offline" end'
# "online"
Return Modified Object
echo '{"severity": "critical"}' | jq '
if .severity == "critical" then
. + {"route": "sentinel", "priority": "high"}
else
. + {"route": "s3", "priority": "low"}
end
'
Output:
{
"severity": "critical",
"route": "sentinel",
"priority": "high"
}
elif (Chained Conditions)
echo '{"level": 7}' | jq '
if .level >= 10 then
"critical"
elif .level >= 7 then
"high"
elif .level >= 4 then
"medium"
else
"low"
end
'
# "high"
Multiple Conditions
echo '{"event": "auth_failure", "severity": "high"}' | jq '
if .event == "auth_failure" then
. + {"mitre": "T1078"}
elif .event == "config_change" then
. + {"mitre": "T1059"}
elif .event == "intrusion" then
. + {"mitre": "T1190"}
else
.
end
'
Compound Conditions
AND
echo '{"severity": "critical", "event": "auth_failure"}' | jq '
if .severity == "critical" and .event == "auth_failure" then
"urgent"
else
"normal"
end
'
# "urgent"
OR
echo '{"severity": "high"}' | jq '
if .severity == "critical" or .severity == "high" then
"escalate"
else
"log"
end
'
# "escalate"
NOT
echo '{"enabled": false}' | jq '
if .enabled | not then
"disabled"
else
"enabled"
end
'
# "disabled"
Complex Logic
echo '{"severity": "high", "source": "ise", "event": "auth_failure"}' | jq '
if (.severity | IN("critical", "high")) and
(.source == "ise") and
(.event | startswith("auth")) then
. + {"route": "sentinel", "alert": true}
else
. + {"route": "s3", "alert": false}
end
'
select()
Filter elements based on condition. Does NOT transform - returns matching elements unchanged or drops them entirely.
Filter Objects
echo '[
{"name":"web-01","status":"active"},
{"name":"web-02","status":"inactive"},
{"name":"db-01","status":"active"}
]' | jq '.[] | select(.status == "active")'
Output:
{"name":"web-01","status":"active"}
{"name":"db-01","status":"active"}
Collect Results
# Wrap in [] to get array
echo '[1,2,3,4,5]' | jq '[.[] | select(. > 3)]'
# [4,5]
select vs if-then-else
| select | if-then-else | |
|---|---|---|
Purpose |
Filter (keep or drop) |
Transform (modify based on condition) |
Output |
Original value or nothing |
Always produces output |
Use When |
You want to exclude non-matching items |
You want to modify items differently |
Example: Same Data, Different Tools
DATA='[{"severity":"critical"},{"severity":"info"},{"severity":"high"}]'
# select: Keep only critical/high (drops info)
echo "$DATA" | jq '[.[] | select(.severity | IN("critical","high"))]'
# [{"severity":"critical"},{"severity":"high"}]
# if-then-else: Transform all (nothing dropped)
echo "$DATA" | jq '[.[] | if .severity | IN("critical","high") then . + {"alert":true} else . + {"alert":false} end]'
# [{"severity":"critical","alert":true},{"severity":"info","alert":false},{"severity":"high","alert":true}]
Alternative Operator //
Provide default when value is null or false.
Default for Missing Field
echo '{"name": "server"}' | jq '.port // 22'
# 22
echo '{"name": "server", "port": 443}' | jq '.port // 22'
# 443
Default for Empty Array
echo '{"items": []}' | jq '.items[0] // "none"'
# "none"
Chain Multiple Defaults
echo '{}' | jq '.primary // .secondary // .default // "fallback"'
# "fallback"
Null vs False
# False triggers alternative
echo '{"enabled": false}' | jq '.enabled // true'
# true
# Use explicit null check if you want to preserve false
echo '{"enabled": false}' | jq 'if .enabled == null then true else .enabled end'
# false
empty
Drop current value (produce no output).
# Drop items based on condition
echo '[1,2,3,4,5]' | jq '.[] | if . > 3 then . else empty end'
Output:
4 5
Equivalent to select
# These are equivalent:
echo '[1,2,3,4,5]' | jq '.[] | select(. > 3)'
echo '[1,2,3,4,5]' | jq '.[] | if . > 3 then . else empty end'
error()
Stop processing with an error message.
echo '{"port": -1}' | jq 'if .port < 0 then error("Invalid port") else . end'
# jq: error (at <stdin>:1): Invalid port
Conditional Validation
echo '{"ip": "not-an-ip"}' | jq '
if (.ip | test("^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$") | not) then
error("Invalid IP address: " + .ip)
else
.
end
'
try-catch
Handle errors gracefully.
# Without try: errors stop processing
echo '{"a":1}' | jq '.b.c' # Error: Cannot index null
# With try: catch the error
echo '{"a":1}' | jq 'try .b.c catch "not found"'
# "not found"
Default on Error
# Try to parse, default on failure
echo '"not json"' | jq 'try fromjson catch .'
# "not json"
Real-World Patterns
MITRE Technique Mapping
echo '{"event_type": "auth_failure"}' | jq '
(
{
"auth_failure": {"id": "T1078", "name": "Valid Accounts"},
"brute_force": {"id": "T1110", "name": "Brute Force"},
"config_change": {"id": "T1059", "name": "Command Execution"}
}[.event_type] // null
) as $mitre |
if $mitre then
. + {"mitre": $mitre}
else
.
end
'
Multi-Condition Routing
echo '{"severity": "critical", "event": "intrusion", "source": "ftd"}' | jq '
# Determine route
(
if .severity == "critical" then "sentinel"
elif .event | IN("auth_failure", "intrusion", "malware") then "sentinel"
elif .source == "ftd" and .action == "deny" then "sentinel"
else "s3"
end
) as $route |
# Determine priority
(
if .severity == "critical" then 1
elif .severity == "high" then 2
elif .severity == "medium" then 3
else 4
end
) as $priority |
. + {"route": $route, "priority": $priority}
'
Safe Field Access
echo '{"user": {"name": "jdoe"}}' | jq '
{
username: (.user.name // "unknown"),
domain: (.user.domain // "local"),
email: (.user.email // (.user.name + "@example.com"))
}
'
Output:
{
"username": "jdoe",
"domain": "local",
"email": "jdoe@example.com"
}
Conditional Field Addition
echo '{"status": "FAILED", "reason": "Timeout"}' | jq '
. + (
if .status == "FAILED" then
{"needs_review": true, "severity": "high"}
else
{}
end
)
'
Practice Exercises
Sample Data
cat << 'EOF' > /tmp/events.json
[
{"type": "auth", "result": "success", "user": "alice"},
{"type": "auth", "result": "failure", "user": "bob", "reason": "wrong password"},
{"type": "config", "action": "change", "user": "admin"},
{"type": "network", "action": "deny", "src": "10.0.0.1"},
{"type": "auth", "result": "failure", "user": "eve", "reason": "account locked"}
]
EOF
Tasks
-
Add "alert: true" to auth failures:
jq '[.[] | if .type == "auth" and .result == "failure" then . + {"alert": true} else . end]' /tmp/events.json -
Keep only events needing attention (failures and config changes):
jq '[.[] | select(.result == "failure" or .type == "config")]' /tmp/events.json -
Add MITRE technique based on type:
jq '[.[] | if .type == "auth" and .result == "failure" then . + {"mitre": "T1078"} elif .type == "config" then . + {"mitre": "T1059"} elif .type == "network" and .action == "deny" then . + {"mitre": "T1071"} else . end ]' /tmp/events.json -
Route to destination:
jq '[.[] | . + { "route": ( if .result == "failure" or .type == "config" or .action == "deny" then "sentinel" else "s3" end ) } ]' /tmp/events.json
Key Takeaways
-
if-then-else-end- Always includeend -
eliffor chains - Cleaner than nested if -
and/or/not- Combine conditions -
select()- Filter (keep or drop) -
//- Default for null/false -
empty- Drop value entirely -
try-catch- Handle errors gracefully
Next Module
Functions - Built-in functions, map, reduce, and custom definitions.