jq + httpie = 💓
Tags: jq, httpie, bash, linux
Published: 2020-06-14 04:20:23

---
title: jq + httpie = 💓
published: true
description: Let's use jq and httpie to print some data to the cli
tags: jq, httpie, bash, linux
---

# Setting up
You'll need to install:
- [jq](https://stedolan.github.io/jq/)
- [httpie](https://httpie.org/)

For this demo, we'll connect to the [Spotify api](https://developer.spotify.com/documentation/web-api/). Create a new Spotify application [here](https://developer.spotify.com/dashboard/applications)

## Client credentials flow
There is a great little plugin for httpie which makes this very easy. It needed a little update. So, for now I'll link [my fork](https://github.com/pcvonz/httpie-oauth2).

To install just clone the repo and run `sudo python setup.py install`

## OAuth2 flow

Spotify uses OAuth2. In order to get your access token to make requests relevant to your spotify account, you'll need to acquire an access token. I used this cli tool written in go to accomplish this. You can find that [here](https://github.com/dcarley/oauth2-cli)

```go
oauth2-cli \
  -id $CLIENT_ID \
  -secret $CLIENT_SECRET \
  -auth https://accounts.spotify.com/authorize \
  -token https://accounts.spotify.com/api/token \
  -scope "user-read-private user-read-email user-library-read"
```

# Making some requests
## Downloading artist images from a search and then opening them

First, we'll make some requests that only require the client credentials flow. 

```bash
#!bin/bash
AUTH_URL="https://accounts.spotify.com/api"
BASE_URL_API="https://api.spotify.com/v1"
CLIENT_ID=$(cat client_id)
CLIENT_SECRET=$(cat client_secret)

http --print="b" --auth-type oauth2 \
              --auth $CLIENT_ID:$CLIENT_SECRET \
              --issuer-uri "$AUTH_URL/token" \
              --scope "user-read-private" \
              GET $BASE_URL_API/search q=="Sophie Hutchings" type=="artist"
```

You should see a big chunk of JSON as output. Couple things to note here:

- `CLIENT_ID=$(cat client_id)`: This is [command substitution](https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html). There is a file in the directory I'm running this script that contains my client id.
- `--print="b"` is telling httpie to print the body of the request only (we'll pipe the body into `jq` later). Other arguments to `--print` are:
  - `H`: Request headers
  - `h` Response headers
  - `B` request body

Let's pipe this into `jq` and just get a list of images from each artist.

```bash
# Beginning lines omitted

## Simple search
ARTISTS=$(http --print="b" --auth-type oauth2 \
     --auth $CLIENT_ID:$CLIENT_SECRET \
     --issuer-uri "$AUTH_URL/token" \
     --scope "user-read-private" \
     GET $BASE_URL_API/search q=="Sophie Hutchings" type=="artist")

# List of artist images
echo $ARTISTS | jq '.artists.items | map(.images | map(.url)) | flatten(1)' 
```

This chunk begins with more command substitution to store the response from before into the `ARTISTS` variable. The contents of the variable is [piped](https://en.wikipedia.org/wiki/Pipeline_\(Unix\)) into `jq` to print a flat list of all the images from the artist we searched on. Let's cover some of the `jq` functions we used:

The simplest filter we used is the "Object Identifier-Index" `.artists.items`. This simply accesses a property within a JSON object. 

```js
{
  "artists": {
    "href": "https://api.spotify.com/v1/search?query=Bad&type=artist&offset=0&limit=20",
      "items": [...] // <--- .artists.items is grabbing this array
  }
}
```

The `items` array is piped into the [map](https://en.wikipedia.org/wiki/Map_\(higher-order_function\)) function. The map function iterates over each item returning a new array of image urls. Let's look at the JSON at this point:

```json
[
  [
    "https://i.scdn.co/image/23009960c33ef08d5973440cca17985a6c70a515",
    "https://i.scdn.co/image/8ff3e392402169f239bce72b3d80c701b75150b8",
    "https://i.scdn.co/image/624dd15f5bdcc1bae5fa47739601f3e0be62ebda",
  ],
  [
    "https://i.scdn.co/image/23009960c33ef08d5973440cca17985a6c70a515",
    "https://i.scdn.co/image/8ff3e392402169f239bce72b3d80c701b75150b8",
    "https://i.scdn.co/image/624dd15f5bdcc1bae5fa47739601f3e0be62ebda",
  ]
]
```

An array of arrays isn't useful for what we're doing next. So we use `flatten` to return a flat array of all the image URLs. 

```json
"https://i.scdn.co/image/23009960c33ef08d5973440cca17985a6c70a515",
"https://i.scdn.co/image/8ff3e392402169f239bce72b3d80c701b75150b8",
"https://i.scdn.co/image/624dd15f5bdcc1bae5fa47739601f3e0be62ebda",
"https://i.scdn.co/image/23009960c33ef08d5973440cca17985a6c70a515",
"https://i.scdn.co/image/8ff3e392402169f239bce72b3d80c701b75150b8",
"https://i.scdn.co/image/624dd15f5bdcc1bae5fa47739601f3e0be62ebda",
```

Nice! Now, let's try to download these and then open them. 

```bash
# Previous lines omitted

IMAGE_URLS=$(echo $ARTISTS | jq -r '.artists.items | map(.images | map(.url)) | flatten(1) | @csv')

# Download and open all the images from a search on artists
echo $IMAGE_URLS | tr ',' '\n' | \
   xargs -I % sh -c 'wget % && xdg-open %' \
```

![Jq images](https://dev-to-uploads.s3.amazonaws.com/i/71gz6psc6hcirgmshgm5.gif)

We modified a couple things in our jq command. 

- `-r`: raw output. In this example it omits `"`
- `@csv`: Omits csv for further processing. Only works on arrays.

The csv output by jq is piped into [tr](https://en.wikipedia.org/wiki/Tr_\(Unix\)). The tr command replaces `,` with new lines. Each line is piped into [xargs](https://en.wikipedia.org/wiki/Xargs). Let's dissect the xargs command:

-`-I`: the replace-str command. The argument is the string that will be replaced by the value from standard input in the final command. So `%` will turn into our image url. 
- `bash -c 'wget % && xdg-open %'`: Starts a new sub shell (each % is replaced by the image url)
- `wget`: Downloads files from the web
- `xdg-open`: Opens a file or URL in the user's preferred application

The result of running this should be a bunch of images being downloaded to your computer and then opened by whatever program is preferred by your user. 

## Inspecting your saved albums and printing all the tracks for each album

```bash
#!bin/bash
AUTH_URL="https://accounts.spotify.com/api"
BASE_URL_API="https://api.spotify.com/v1"
CLIENT_ID=$(cat client_id)
CLIENT_SECRET=$(cat client_secret)

# You'll have to run this first. Copy the access token to `auth_token` in the same directory as this script
# oauth2-cli \
#   -id $CLIENT_ID \
#   -secret $CLIENT_SECRET \
#   -auth https://accounts.spotify.com/authorize \
#   -token https://accounts.spotify.com/api/token \
#   -scope "user-library-read"

# Get a list of albums from your library
ALBUMS=$(http GET $BASE_URL_API/me/albums \
  Authorization:"Bearer $(cat auth_token)")

# List saved albums from your spotify profile and their artists
echo $ALBUMS | jq '.items | map({ "name": .album.name, "artists": .album.artists | map(.name)})'

# Get tracks from your albums
ALBUM_IDS=$(echo $ALBUMS | jq -r '.items | map(.album.id) | @tsv' )

## Separtes each album by a newline, for further processing and then make another request for each album to get a list of tracks
echo $ALBUM_IDS | tr ' ' '\n' | xargs -I % sh -c 'http --print="b" GET https://api.spotify.com/v1/albums/%/tracks \
  Authorization:"Bearer $(cat auth_token)" | jq ".items | map({ "name": .name, "artists": .artists | map(.name)})"'

```

Everything we covered in the previous section is used in here too! One difference is that we use map to return a list of objects instead. 

## Final thoughts

Using jq with httpie is powerful once you get the hang of using some of the linux utilities and idiosyncrasies of shell scripting. I'm still learning new stuff everyday. If you have any thoughts on how to improve this workflow, please share!