Skip to content
shellmap

Parse JSON from a shell

Extract specific fields from a JSON blob (a curl response, kubectl output, log line) and pipe them into other shell tools — the everyday "grab the .token / .items[].name / .data.url" workflow that differs sharply from just pretty-printing.

How to parse json from a shell in each shell

Bashunix
curl -s api.example.com/me | jq -r '.email'

**`-r` is the field that makes jq a pipe-able tool** — without it, jq emits JSON-quoted strings (`"[email protected]"` with literal quotes), so `mail $(jq .email)` sends to the wrong address. With `-r` (raw output): `[email protected]`. The selector DSL: `.field` (one key), `.field.subfield` (nested), `.items[]` (iterate array), `.items[0]` (index), `.items | length` (count), `.items[] | select(.active)` (filter), `.users[] | "\(.name) <\(.email)>"` (string-interpolate). For injecting shell variables INTO a jq filter, NEVER concatenate (injection risk): use `--arg` for strings (`jq --arg u "$USER" '.users[] | select(.name==$u)'`) or `--argjson` for non-string values (`jq --argjson n 5 '.items[$n]'`).

Zshunix
curl -s api.example.com/me | jq -r '.email'

Same external `jq`. Install: `brew install jq` (macOS), `apt install jq` (Debian/Ubuntu), `apk add jq` (Alpine). For "if jq is not installed" graceful fallback: `python3 -c "import sys,json; print(json.load(sys.stdin)['email'])"` works on virtually every Linux/macOS box. For YAML/TOML, `yq` (Python rewrite) extends jq syntax to other formats — same `-r` flag. `jaq` is a Rust rewrite of jq with the same query language — faster on large files but not always packaged in distros.

Fishunix
curl -s api.example.com/me | jq -r '.email'

Same external. Fish-native interpolation makes jq pipelines tidy: `set email (curl -s api.example.com/me | jq -r .email); echo "got: $email"`. Fish doesn't need the bash `$(...)` subshell syntax — `(...)` is the same idea, cleaner inside double-quoted strings. For "extract many fields at once into separate vars": `set name email (curl -s … | jq -r ".name, .email")` — jq emits one value per line; fish `set` with multiple names splits by newline.

PowerShellwindows
(Invoke-RestMethod api.example.com/me).email

Pwsh skips the jq middleman — `Invoke-RestMethod` auto-deserializes JSON to nested PowerShell objects, then normal dot access works (`.email`, `.users[0].email`, `.items.Where({$_.active})`). For STRING-input JSON: `$json | ConvertFrom-Json`. **The #1 footgun: pwsh property access is CASE-INSENSITIVE by default** (`$obj.EMAIL` and `$obj.email` both work) — JSON spec says keys are case-sensitive. If your JSON has both `Id` and `id` keys, default `[PSCustomObject]` SILENTLY COLLAPSES them; use `-AsHashtable` (pwsh 6+) to preserve case-sensitivity: `$json | ConvertFrom-Json -AsHashtable`. For "one specific field, raw string out": just dot-access — no quoting issue like jq has (since pwsh strings have no implicit quotes). For "filter then extract": `($json | ConvertFrom-Json).items | Where-Object active | ForEach-Object email`.

cmd.exewindows
for /f %i in ('powershell -NoProfile -Command "(Invoke-RestMethod api.example.com/me).email"') do set EMAIL=%i

cmd has NO native JSON parser. The portable answer is to shell out to pwsh (above) and capture via `for /f`. Watch the quote-escaping: cmd's `for /f` parses the pwsh output by whitespace (one token per line); `delims=` disables tokenization for full-line capture. If jq is installed on Windows (`winget install jqlang.jq`): `curl -s api.example.com/me | jq -r ".email"` works in cmd too — same syntax as bash. For a one-shot lookup, just run the pwsh command directly; for a `.bat` that needs the value: the `for /f` capture pattern.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **`-r` (raw output) is the difference between jq-as-explorer and jq-as-pipe-tool**: by default jq emits valid JSON (strings get quotes, numbers stay numbers). `-r` strips the JSON quoting on string outputs only — numbers / objects / arrays still emit as JSON. This is what lets `echo $JSON | jq -r .url | xargs curl` work (without `-r` you'd curl a URL with literal quotes). For arrays of strings → newline-separated: `jq -r ".items[]"`. For arrays of strings → null-separated (safe for filenames with newlines): `jq -rj '.items[] + "\u0000"'` then `xargs -0`.
  • **Streaming vs whole-file**: jq loads the entire input into memory by default — fine up to ~hundreds of MB, painful past that. For multi-GB JSON dumps: `jq --stream` emits path-value pairs as a flat sequence (`[["users",0,"name"],"alice"]` etc) — composable but verbose. The pragmatic split: if input is `{"records":[...]}` use `jq -c .records[]` once to one-object-per-line, then process with line-oriented tools (`grep`, `awk`, `parallel`). Pwsh: `ConvertFrom-Json` is also whole-file — for streaming, drop to `[Newtonsoft.Json.JsonTextReader]` (pwsh 5.1) or `[System.Text.Json.JsonDocument]` (pwsh 7.4+) and walk manually.
  • **Case-sensitivity divergence**: jq is case-SENSITIVE on JSON keys (matching the spec); pwsh `ConvertFrom-Json` default `[PSCustomObject]` is case-INSENSITIVE on property access (because pwsh itself is case-insensitive). Symptom: `$json.Id` and `$json.id` both return the same value, and if the source JSON had BOTH keys, only one survives (silently). Fix: pass `-AsHashtable` (pwsh 6+, returns ordered case-sensitive hashtable) when round-tripping JSON whose keys you don't control. Bonus quirk: pwsh 5.1 throws on duplicate keys; pwsh 6+ silently overwrites — yet another reason to prefer `-AsHashtable` for untrusted input.
  • **Auto-coercion traps**: pwsh `ConvertFrom-Json` auto-converts ISO-8601-LOOKING strings to `[DateTime]` (`"2026-05-16T14:00:00Z"` becomes a DateTime object, not a string). On round-trip via `ConvertTo-Json` the timezone may be dropped or shifted depending on `.Kind`. pwsh 7.4+ adds `-DateKind String` to suppress this. jq has no such coercion — strings stay strings. If you parse → modify → re-emit JSON in pwsh and find datestamps drifting, this is why; pass `-DateKind String` or treat dates as raw strings throughout.

Related commands

Related tasks