Skip to content
Commitpedia
Log in Join & edit

jq recipes: 23 one-liners for JSON on the command line

A runnable collection of jq filters for everyday JSON work: pick fields, filter with select, reshape, sort, count, group, pass shell variables, and export CSV.

Edit history of jq recipes: 23 one-liners for JSON on the command line: 1 recorded edit
Edit history · 1 edit
Type
Script
Edits
1
Editors
1
First issued
15 Oct 2026
Last edit
15 Oct 2026
Language
bash
Licence
CC0-1.0

jq is a small command-line program that reads JSON, applies a filter, and prints the result. This script runs each recipe against a built-in sample and prints the command next to its output, so you can see exactly what each filter does.

Install jq

Check before you run this

  • Runs with administrator rights. Make sure you understand every line.
brew install jq          # macOS (Homebrew)
sudo apt install jq      # Debian, Ubuntu
winget install jqlang.jq # Windows

The basics in one minute

  • . is the whole input, .name is a field, .users[0] is the first array element, and .users[] outputs each element separately.
  • | passes the output of one filter into the next, like a shell pipe.
  • Wrap a filter in [ ... ] to collect its outputs into one array.
  • -r prints strings without quotes, which is useful in shell scripts. -c prints each result on one line.
  • Put the filter in single quotes so the shell does not interpret $, | or [].
curl -s https://api.github.com/repos/jqlang/jq | jq '.stargazers_count'
jq -r '.users[].email' users.json

The script

Save it as jq-recipes.sh, then run bash jq-recipes.sh. To try a recipe on your own data, copy the filter and run jq '<filter>' yourfile.json.

#!/usr/bin/env bash
set -euo pipefail

command -v jq >/dev/null || { echo "jq is not installed" >&2; exit 1; }

SAMPLE='{
  "store": "demo",
  "users": [
    {"id": 1, "name": "Ada",   "email": "[email protected]", "tags": ["admin", "dev"], "active": true,  "orders": 3},
    {"id": 2, "name": "Brian", "email": null,              "tags": ["dev"],          "active": false, "orders": 0},
    {"id": 3, "name": "Chen",  "email": "[email protected]", "tags": [],              "active": true,  "orders": 5}
  ]
}'

run() {
  local title=$1; shift
  local shown="jq" arg
  for arg in "$@"; do
    if [[ $arg == -* ]]; then shown+=" $arg"; else shown+=" '$arg'"; fi
  done
  printf '\n### %s\n$ %s\n' "$title" "$shown"
  printf '%s\n' "$SAMPLE" | jq "$@"
}

run "Pretty-print" '.'
run "One field" '.store'
run "Raw string, no quotes" -r '.store'
run "Every element of an array" '.users[]'
run "One field from each element" -r '.users[].name'
run "First and last element" '.users[0], .users[-1]'
run "Count elements" '.users | length'
run "Filter with select" '.users[] | select(.active)'
run "Filter by a number" -c '.users[] | select(.orders >= 3) | {name, orders}'
run "Keep results as an array" -c '[.users[] | select(.active) | .name]'
run "Reshape objects" -c '.users | map({id, contact: .email})'
run "Default for null" -r '.users[] | .email // "no email"'
run "Array contains a value" -r '.users[] | select(.tags | index("admin")) | .name'
run "Sort and take top N" -c '.users | sort_by(-.orders) | .[:2] | map(.name)'
run "Sum a field" '[.users[].orders] | add'
run "Group and count" -c '[.users[].tags[]] | group_by(.) | map({tag: .[0], count: length})'
run "List keys" -c '.users[0] | keys'
run "Add or change a field" -c '.users[0] | .vip = true | del(.tags)'
run "Use a shell variable safely" -c --arg who "Chen" '.users[] | select(.name == $who)'
run "Objects to CSV" -r '.users[] | [.id, .name, (.email // "")] | @csv'
run "Objects to TSV" -r '.users[] | [.id, .name] | @tsv'
run "Compact one line per item (JSON Lines)" -c '.users[]'
run "Find every key path" -c '[paths(scalars)] | .[:4]'

Sample output

### Group and count
$ jq -c '[.users[].tags[]] | group_by(.) | map({tag: .[0], count: length})'
[{"tag":"admin","count":1},{"tag":"dev","count":2}]

Notes

  • select(cond) passes an input through only when the condition is true. map(f) is shorthand for [.[] | f].
  • a // b returns b when a is null or false. It is handy for defaults, but remember that false is replaced too.
  • Never paste shell variables into the filter string. Pass them with --arg name value (always a string) or --argjson name 42 (parsed as JSON), then use $name.
  • @csv needs an array of simple values and quotes strings for you. @tsv escapes tabs and line breaks.
  • jq never edits a file in place. Write to a temporary file and move it: jq '.version = "2"' app.json > tmp.json && mv tmp.json app.json. Redirecting straight back to app.json empties it before jq reads it.
  • For JSON Lines input, jq already processes each object in turn. Add -s (slurp) to read them all into one array first.

See also

  • 0057 Shell cheat sheet: everyday commands A quick reference for moving around, managing files, viewing and searching text, checking processes and disk space in bash and zsh, with… Beginner · Reference · Linux, macOS Article Basics · Command-line tools 15 Oct 2026

Repos in the wild

History

Every saved edit is kept. The plot on the sleeve draws one line per edit. Open any edit to see exactly what changed.

  1. commitpedia-admin +5,234 chars

Report a problem

Something wrong, unsafe or copied? Tell the editors.
What is wrong?