jq/yq

jq/yq

Attribute Value

Goal

Expert structured data processing

Interest Link

Systems Tools > Data Processing

Status

In Progress

Application

API responses, Kubernetes, configuration

Documentation

domus-linux-ops jq-yq-mastery

Skill Areas

Area Description Status

Selection

.field, .[index], .[]

[x] Proficient

Filters

select(), map(), keys

[x] Proficient

Construction

{}, [], string interpolation

[ ] In Progress

Advanced

reduce, group_by, recurse

[ ] Learning

yq

YAML processing, conversion

[ ] In Progress

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'

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/" + .)}]'

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}}