The Setup
If you work with JSON APIs, configs, or log processing, you have probably used jq in a shell script. It is solid. But there are a few edge cases that trip people up regularly — things that work fine in tests but break in production.
Here is what I have run into.
The Problem: Null vs. Missing Keys
Say you have JSON like this:
{"name": "web01", "status": "running"}
And you want to get the status field. Easy:
jq -r ".status" <<< "{\"name\": \"web01\", \"status\": \"running\"}"
# returns: running
But what if the field is missing or null?
{"name": "web01"}
jq will return null. Your script might handle that fine — or it might not. The problem comes when you try to do arithmetic or comparisons on null.
jq ".count + 1" <<< "{\"count\": null}"
# returns: null
Not helpful. You need to handle this explicitly:
jq -r ".count // 0 + 1" <<< "{\"count\": null}"
# returns: 1
The // operator provides a default value when the left side is null or missing.
The Problem: Piping Arrays Correctly
Another one. You have an array of objects and want to filter them:
[{"host": "web01", "cpu": 45}, {"host": "web02", "cpu": 12}, {"host": "web03", "cpu": 78}]
You want hosts with cpu > 50.
jq ".[] | select(.cpu > 50)" <<< "[{\"host\": \"web01\", \"cpu\": 45}, {\"host\": \"web02\", \"cpu\": 12}, {\"host\": \"web03\", \"cpu\": 78}]"
This outputs:
{"host": "web03", "cpu": 78}
Fine, right? But now try to get just the hostnames as a list:
jq "[.[] | select(.cpu > 50) | .host]" <<< "[{\"host\": \"web01\", \"cpu\": 45}, {\"host\": \"web02\", \"cpu\": 12}, {\"host\": \"web03\", \"cpu\": 78}]"
Returns:
["web03"]
Works. But if nothing matches:
jq "[.[] | select(.cpu > 100) | .host]" <<< "[{\"host\": \"web01\", \"cpu\": 45}]"
Returns:
[]
Not null — an empty array. Your script might check for null and think "no data" when it is actually "zero results". Different things.
The Workaround
When I write jq in shell scripts that handle production data, I usually wrap things defensively:
# Get a value with a default, even if key is missing
cpu=$(echo "$json" | jq -r ".cpu // \"unknown\"")
# Check if result is valid before proceeding
if [ "$cpu" = "null" ] || [ "$cpu" = "unknown" ]; then
echo "No CPU data" >&2
exit 1
fi
Or I use jq -e to check for null/empty results:
jq -e ".cpu" <<< "{\"cpu\": null}" > /dev/null
if [ $? -eq 5 ]; then
echo "Key not found"
fi
Error code 5 means the output was null.
The Point
jq is reliable. But when you mix shell scripting with JSON processing, you hit edge cases around null handling and empty results. A few defensive patterns save you from late-night debugging sessions.
These are not jq bugs — they are just things to know when you are writing scripts that process real data.
Schiff Heimlich | Sysadmin who learned this the hard way
Top comments (0)