# JSON with jq

jq reads JSON, the labeled data that web APIs and cloud tools answer with: dots follow the labels in, .\[\] goes through every item in a list, select keeps the items you want, and -r prints plain lines a bash loop can read.

Journeyman · Unix · Linux · Updated November 10, 2026

## After this lesson you can

- Pick values out of JSON with jq, using .name, .\[\] and select
- Turn JSON into plain lines with jq -r, and loop over them with while read

## Key takeaways

- JSON is labeled data: { } holds named values, [ ] holds a list, and either can sit inside the other.
- jq '.properties.periods[0].name' follows the labels in from the outside, one dot at a time. A list's items count from 0.
- .[] goes through every item in a list, and the pipe inside jq's quotes hands each item to the next step.
- select( ) keeps only the items whose test is true, such as select(.probabilityOfPrecipitation.value >= 50).
- -r prints text without its quotes, so jq's lines can feed a while read loop.

Before you bike to class, you want one answer: will it rain this week, and when? Weather services answer that question for programs too, through an **API**, a web address made for programs instead of people (*Talking to the Web: curl* asks one live). The answer comes back as JSON, the text format most of the web's data travels in. Cloud tools answer in JSON too: an administrator who lists a company's cloud servers from the terminal gets pages of it. `grep` and `awk` see JSON as lines of text, but it isn't shaped in lines. `jq` reads it the way it's built. If `jq --version` doesn't print a version number yet, install it as in *Installing Tools: Homebrew and apt*: `sudo apt install jq`, or `brew install jq` on a Mac.

## A saved forecast

This is a real forecast from the US National Weather Service, for New York's Central Park, cut down to the parts this lesson uses and saved in a file, so it works without the internet and gives the same answers every time. The lines are safe to run again.

```bash
mkdir -p ~/terminal-practice/weather && cd ~/terminal-practice/weather
printf '%s\n' '{ "properties": { "periods": [' \
  '{"name": "Today", "temperature": 58, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 4}, "shortForecast": "Mostly Sunny"},' \
  '{"name": "Tonight", "temperature": 45, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 1}, "shortForecast": "Mostly Clear"},' \
  '{"name": "Tuesday", "temperature": 61, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 1}, "shortForecast": "Mostly Sunny"},' \
  '{"name": "Tuesday Night", "temperature": 47, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 0}, "shortForecast": "Clear"},' \
  '{"name": "Veterans Day", "temperature": 61, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 0}, "shortForecast": "Sunny"},' \
  '{"name": "Wednesday Night", "temperature": 47, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 1}, "shortForecast": "Mostly Cloudy"},' \
  '{"name": "Thursday", "temperature": 59, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 45}, "shortForecast": "Mostly Cloudy then Chance Light Rain"},' \
  '{"name": "Thursday Night", "temperature": 45, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 79}, "shortForecast": "Light Rain"},' \
  '{"name": "Friday", "temperature": 52, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 79}, "shortForecast": "Light Rain"},' \
  '{"name": "Friday Night", "temperature": 39, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 54}, "shortForecast": "Chance Light Rain"},' \
  '{"name": "Saturday", "temperature": 51, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 29}, "shortForecast": "Chance Light Rain"},' \
  '{"name": "Saturday Night", "temperature": 38, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 12}, "shortForecast": "Partly Cloudy"},' \
  '{"name": "Sunday", "temperature": 53, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 4}, "shortForecast": "Mostly Sunny"},' \
  '{"name": "Sunday Night", "temperature": 39, "temperatureUnit": "F", "probabilityOfPrecipitation": {"value": 0}, "shortForecast": "Partly Cloudy"}' \
  '] } }' > forecast.json
jq . forecast.json | head -n 12
```

```text
{
  "properties": {
    "periods": [
      {
        "name": "Today",
        "temperature": 58,
        "temperatureUnit": "F",
        "probabilityOfPrecipitation": {
          "value": 4
        },
        "shortForecast": "Mostly Sunny"
      },
```

Think of JSON as a storage box with labeled boxes inside, and more boxes inside those. `{ }` (read "braces") holds **named values**: a label in double quotes, a colon, and the value. `[ ]` (read "square brackets") holds a **list**. Here the whole file is one box holding `properties`, which holds `periods`, a list of 14 forecasts, one for each day and night of the week. `jq .` (read "jq dot") prints the whole thing laid out with indents, and in colors on a terminal; the dot means "all of it".

## Following the labels: dots

What jq should pick out is its **filter**, in single quotes before the file name. A dot and a label goes into that label, and `[0]` (read "item zero") takes a list's first item, because lists count from 0:

```bash
jq '.properties.periods[0].name' forecast.json
jq '.properties.periods[0].probabilityOfPrecipitation.value' forecast.json
```

```text
"Today"
4
```

Read the first filter aloud: "properties, then periods, item zero, then its name". Text comes back in double quotes, numbers without. Misspell a label and jq prints `null`, its word for "nothing there"; check the spelling against `jq .`.

![The forecast drawn as nested boxes, one inside another: the outside box, then properties, then the list periods with its items numbered 0, 1, 2 and so on, then item 0 with its labels name, temperature and shortForecast. The jq filter that reads the name of item 0 of periods inside properties is written above, each piece over the box it opens, and an arrow ends at "Today"](https://fsdtrainingmedia.blob.core.windows.net/public/images/2026/11/4fd0a8d07f9f4f998b3323ca5e3ff6ae.webp)

## Every item: .\[\] and jq's own pipe

`[]` with nothing inside means every item in the list, one after another:

```bash
jq '.properties.periods[].name' forecast.json | head -n 4
```

```text
"Today"
"Tonight"
"Tuesday"
"Tuesday Night"
```

The `| head -n 4` is bash's pipe, outside the quotes. jq has its own pipe, inside the quotes, and it works the same way: `.properties.periods[] | .name` hands each item to the next step, which takes its name. With one step that's the same filter as above; with more steps, the pipe keeps a filter readable. Leave out the `[]` and jq stops with an error instead: `periods` is a list, and a list has no name of its own.

## Only some: select

Now the question you opened the forecast for. `select( )` keeps the items whose test is true and drops the rest. `>=` is read "is at least":

```bash
jq '.properties.periods[] | select(.probabilityOfPrecipitation.value >= 50) | .name' forecast.json
```

```text
"Thursday Night"
"Friday"
"Friday Night"
```

Rain from Thursday night through Friday night: take the bus on Friday. `==` tests for "is equal to", so `select(.name == "Friday") | .shortForecast` gives `"Light Rain"`.

## Lines for bash: -r

The quotes get in the way of a script. `-r` (read "raw") prints text without them. And `"\(.temperature) \(.name)"` builds one line for each item: `\( )` (read "backslash parentheses") puts a value into the text, the way `$` does in a bash string in double quotes. Those lines are ready for `while read` from *Line by Line: while read*, with the first word going into `temp` and the rest of the line into `name`:

```bash
jq -r '.properties.periods[] | "\(.temperature) \(.name)"' forecast.json | while read -r temp name; do
  if [[ $temp -lt 40 ]]; then
    echo "Coat weather: $name, $temp°F"
  fi
done
```

```text
Coat weather: Friday Night, 39°F
Coat weather: Saturday Night, 38°F
Coat weather: Sunday Night, 39°F
```

The number goes first on purpose: a name such as `Friday Night` has a space in it, and the last name in `read` keeps the rest of the line, space and all. This is the usual shape of a script that works with JSON: jq picks the data out and makes lines of it, and bash does something with each line.

## Check yourself

2 questions to try on the lesson page.

## So what

APIs, cloud tools and many apps speak JSON. With jq, a few dots and a `select` pick out exactly what you need, and `-r` turns it into lines your script can work through.

## Key terms

- [JSON](https://www.ask-codey.com/glossary/json)
- [jq](https://www.ask-codey.com/glossary/jq)
- [API](https://www.ask-codey.com/glossary/api)

## Cheat sheets

- [JSON with jq](https://www.ask-codey.com/cheat-sheets/bash-jq-cheat-sheet)

## Related

- [CSV Files in Bash, and Their Limits](https://www.ask-codey.com/lessons/bash-csv-files)
- [Talking to the Web: curl](https://www.ask-codey.com/lessons/bash-curl)
- [Line by Line: while read](https://www.ask-codey.com/lessons/bash-while-read)

Source: https://www.ask-codey.com/lessons/bash-jq
