Skip to content

First Filters

A jq filter is a path through the data, more often than not. You write where you want to go, and jq walks there. This chapter is the walking: into objects, into arrays, through every element at once — plus the small inspection tools (keys, length, type) that tell you what you're looking at when the path isn't obvious.

Into objects: .name

A dot followed by a key reads that key's value:

$ jq '.models[0].name' models.json
"ember-2:7b"

Read it left to right: the input document, its models value, that array's element 0, that object's name. Paths chain as deep as the data goes:

$ jq '.models[1].details.context_length' models.json
32768

Two things to notice about the output. Strings come out quoted — "ember-2:7b" is JSON, and jq's default output is JSON. (Chapter 4's -r flag strips the quotes when a human or a shell script wants the bare text.) And numbers come out bare. The output is always valid JSON until you ask otherwise.

Missing keys are null, not errors

Ask for a key that isn't there and jq answers null:

$ jq '.models[3].capabilities' models.json
null

$ jq '.models[0].favourite_colour' models.json
null

$ jq '.inventory' models.json
null

The first of those matters: kestrel-1:1.5b, the fourth model, genuinely has no capabilities key in the file. jq doesn't distinguish "absent" from "present but null" here, and it doesn't stop the run. That is mostly kindness — filters can probe ragged data without dying — and it cuts the other way too: a typo'd key also returns null, quite happily, forever. Chapter 3 shows has, which checks presence for real, and chapter 10 is about the debugging this behaviour calls for.

Indexing into null is fine as well — .inventory above was null, and .inventory.name would also be null, not an error. jq only gets loud when you try to iterate or do arithmetic on nothing, which is chapter 10's material.

Into arrays: .[0], and every element at once

Arrays are indexed from zero, and negative indexes count from the end:

$ jq '.models[-1].name' models.json
"harbor-4:70b"

A slice takes a range: .models[0:2] is the first two models. But the workhorse is .[] — "every element, one at a time." Piping the stream through another path runs that path once per element:

$ jq '.models[] | .name' models.json
"ember-2:7b"
"ember-2:32b"
"kestrel-1:8b"
"kestrel-1:1.5b"
"harbor-4:70b"

That | is the same pipe idea as the shell's, one level down: each value that comes out of the left side flows into the right side. Five models in, five names out. Much of jq is this one move — explode an array with .[], then handle the pieces individually.

If a value might be missing or not an array, .[]? iterates what's there and stays quiet about what isn't:

$ jq '.models[3].capabilities[]?' models.json

No output at all — the missing capabilities of kestrel-1:1.5b produced nothing, not even null. Hold that thought; the un-? version of this command is chapter 10's first error.

Looking at what you have: keys, length, type

When the data is unfamiliar, three builtins answer most questions. keys lists an object's keys (sorted, whatever the file's order):

$ jq '.models[0] | keys' models.json
[
  "capabilities",
  "details",
  "digest",
  "model",
  "modified_at",
  "name",
  "size"
]

length counts — array elements, object keys, or string characters, depending on what's on the left:

$ jq '.models | length' models.json
5

$ jq '.models[0].name | length' models.json
10

$ jq '.models[0].details | length' models.json
8

type names the kind of value, which matters when a filter misbehaves and you want to know what it was actually fed:

$ jq '.models | type' models.json
"array"

$ jq '.models[0].size | type' models.json
"number"

The full set of answers is "object", "array", "string", "number", "boolean", and "null" — chapter 3 puts type to work inside selections.

Taking objects apart: to_entries

One more path tool, worth meeting early because later chapters lean on it. to_entries turns an object into an array of {"key": …, "value": …} pairs:

$ jq '.models[0].details | to_entries | .[3]' models.json
{
  "key": "family",
  "value": "ember"
}

Once an object is an array, everything jq does to arrays — iterate, filter, count — works on it. Its mirror, from_entries, rebuilds the object when you're done.