Sorting, grouping and aggregating

jq · Practical · 10 min

Quick counts, totals and top-N lists from JSON data answer many questions without a spreadsheet or script.

jq has the aggregation tools you need for quick analysis. They all work on arrays, so start from an array (or collect a stream with [...] or -s).

  • sort and sort_by(.field) order elements; sort_by(.a, .b) sorts by several keys; reverse flips the result for descending order.
  • group_by(.field) sorts and splits the array into an array of groups, each an array of elements with the same key.
  • unique and unique_by(.field) remove duplicates (and sort).
  • min_by(.f) / max_by(.f) return the element with the smallest / largest value; min / max work on plain values.
  • add sums numbers, concatenates strings or arrays, merges objects. add / length is the average.
  • length counts; first, last, limit(n; expr) take parts.

The classic count-by pattern:

group_by(.status) | map({status: .[0].status, count: length})

It yields one object per status. To get a dictionary instead, build entries: map({key: .[0].status, value: length}) | from_entries.

Top N: sort_by(.ms) | reverse | .[:3]. For newline-delimited logs, slurp them first: jq -s 'group_by(.level) | map({level: .[0].level, n: length})' app.log.

Example

cat > s.json <<'EOF'
[{"svc":"api","status":"ok","ms":120},{"svc":"web","status":"err","ms":900},
 {"svc":"api","status":"err","ms":40},{"svc":"api","status":"ok","ms":300}]
EOF
jq -c 'group_by(.status) | map({status: .[0].status, count: length})' s.json
# [{"status":"err","count":2},{"status":"ok","count":2}]
jq -c 'group_by(.svc) | map({key: .[0].svc, value: (map(.ms) | add / length)}) | from_entries' s.json
# {"api":153.33333333333334,"web":900}
jq -c 'sort_by(.ms) | reverse | .[:2] | map(.ms)' s.json     # [900,300]
jq -c 'max_by(.ms) | .svc' s.json                            # "web"
jq -c 'map(.svc) | unique' s.json                            # ["api","web"]
jq 'map(.ms) | add' s.json                                   # 1360

Remember

  • sort_by, group_by, unique_by, min_by/max_by work on arrays
  • group_by(.k) | map({k: .[0].k, n: length}) counts per key
  • add sums; add / length averages

Watch out for

  • Calling group_by on a stream (.[] | group_by...) instead of on the array itself.

Check yourself

  1. What does `group_by(.t)` return for `[{"t":"a"},{"t":"b"},{"t":"a"}]`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: jqlang.org/manual/