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.

Basic Filter

echo '[1,2,3,4,5]' | jq '.[] | select(. > 3)'

Output:

4
5

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

  1. Add "alert: true" to auth failures:

    jq '[.[] | if .type == "auth" and .result == "failure" then . + {"alert": true} else . end]' /tmp/events.json
  2. Keep only events needing attention (failures and config changes):

    jq '[.[] | select(.result == "failure" or .type == "config")]' /tmp/events.json
  3. 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
  4. Route to destination:

    jq '[.[] |
      . + {
        "route": (
          if .result == "failure" or .type == "config" or .action == "deny" then
            "sentinel"
          else
            "s3"
          end
        )
      }
    ]' /tmp/events.json

Key Takeaways

  1. if-then-else-end - Always include end

  2. elif for chains - Cleaner than nested if

  3. and/or/not - Combine conditions

  4. select() - Filter (keep or drop)

  5. // - Default for null/false

  6. empty - Drop value entirely

  7. try-catch - Handle errors gracefully

Next Module

Functions - Built-in functions, map, reduce, and custom definitions.