← back to latest

Pull only the matching records out of a JSON file

jq -r '.servers[] | select(.cpu > 60) | [.name, .ip, .cpu] | @tsv' data.json

anatomy

jq
A filter language for JSON. It reads values from the input, runs them through a program, and prints whatever values come out the other end. A program can emit zero, one, or many values per input, and that is what makes filtering possible.
-r
Raw output. Strings print without their surrounding quotes and escapes. Without it the @tsv line prints as "web-02\t10.0.1.11\t62", a JSON string with literal backslash-t sequences instead of tabs.
.servers[]
Take the servers array and emit each element as a separate value. The [] is an iterator, so everything after the next pipe runs once per server, not once on the whole array.
select(.cpu > 60)
Pass the current value through unchanged if the condition is true, and emit nothing at all if it is false or null. On its own, .cpu > 60 prints true or false for every server. select turns that boolean into a gate.
[.name, .ip, .cpu]
Build a new array from three fields of the surviving object. The order you list them in is the column order you get, regardless of key order in the source file.
@tsv
Format an array as one tab-separated line, escaping any tabs or newlines inside the values. That makes the output safe to hand to cut, sort, awk, or a while read loop.
data.json
The input file. Leave it off and jq reads stdin, which is how it usually sits at the end of a curl or kubectl pipeline.

Sample output

web-02	10.0.1.11	62
api-01	10.0.2.10	78
db-01	10.0.3.10	91

Three of the five servers are above 60 percent CPU, and the output is plain tab-separated text with no JSON left in it. db-01 at 91 is the one to look at first; pipe through sort -t$'\t' -k3 -nr if the list gets longer.

When you would reach for it

An API response with a hundred objects when you care about four of them, a kubectl get -o json dump, a cloud inventory export, or a config file you need to query from a script. grep finds lines, but JSON objects span lines and their fields can appear in any order, so a text search either misses records or matches the wrong field. select asks the question against the parsed structure instead.

Gotchas

  • Types matter, and a mismatch fails silently. select(.cpu > "60") returns nothing and exits 0, because jq orders every number before every string, so no number is ever greater than "60". The same thing happens with --arg t 60, which passes the value in as a string. Use --argjson for numbers.
  • A missing field is null, and null > 60 is false. A typo like select(.cpus > 60) drops every record without an error. If the output is unexpectedly empty, check the field name with jq '.servers[0] | keys' data.json.
  • An empty result exits 0 by default, so a script cannot tell “no matches” from “matched”. Add -e and jq exits 4 when the program produces no output, or 1 when the last value is false or null.
  • Debian, Ubuntu, and Alpine base images do not include jq: apt install jq or apk add jq. Debian 12 ships jq 1.6, which lacks newer builtins such as pick. The filter in this entry works on 1.6 and later.

Variants

$ jq '[.servers[] | select(.cpu > 60)] | length' data.json

Count the matches instead of listing them. The outer brackets collect every emitted value back into one array so length can measure it

$ t=60; jq -r --argjson t "$t" '.servers[] | select(.cpu > $t) | .name' data.json

Pass the threshold in from a shell variable. Never splice $t into the jq program with double quotes; --argjson keeps it a number and keeps the quoting sane

$ jq -r '.servers[] | select(.role == "frontend" and .cpu > 50) | .name' data.json

Combine conditions with and and or. String comparisons need the double quotes inside the single-quoted program

lineage

Stephen Dolan wrote jq and released it in 2012, describing it as sed for JSON. The first version was written in C with a small functional language borrowed in spirit from Haskell and the Unix pipe: every expression is a filter, and filters compose with |. It spread through operations work at the same pace as JSON APIs, and by the mid-2010s it was the default answer to any question about parsing JSON in a shell script. Releases slowed after 1.6 in 2018 until a group of community maintainers took over under the jqlang organization and shipped 1.7 in 2023. Apple began including jq in macOS with Sequoia, which is why a Mac now has it out of the box while most Linux base images still do not.