Tutorial

Adapted from the jq tutorial

GitHub has a JSON API, so let’s play with that. This URL gets us the last five commits from the Retrospring repo.

curl 'https://api.github.com/repos/Retrospring/retrospring/commits?per_page=5'
Show result
[
  {
    "sha": "b6c42264a6652674deb66e7dd9b95a784fea8e40",
    "node_id": "C_kwDOAk1rYNoAKGI2YzQyMjY0YTY2NTI2NzRkZWI2NmU3ZGQ5Yjk1YTc4NGZlYThlNDA",
    "commit": {
      "author": {
        "name": "Georg Gadinger",
        "email": "nilsding@nilsding.org",
        "date": "2022-07-09T11:40:29Z"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "date": "2022-07-09T11:40:29Z"
      },
      "message": "Merge pull request #507 from Retrospring/depperterbot\n\nDependabot merge branch",
      "tree": {
        "sha": "eea1f8de390ac9f9adadbaecfc367ec5bacff33e",
        "url": "https://api.github.com/repos/Retrospring/retrospring/git/trees/eea1f8de390ac9f9adadbaecfc367ec5bacff33e"
      },
      "url": "https://api.github.com/repos/Retrospring/retrospring/git/commits/b6c42264a6652674deb66e7dd9b95a784fea8e40",
      "comment_count": 0,
      "verification": {
        "verified": true,
        "reason": "valid",
        "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJiyWktCRBK7hj4Ov3rIwAAhYYIAIAOf3QibMAo38jRRSqmFgf0\nTAwJmW01Vkp9dAcQgUvMAwopyN+w4pFt3JgXh4hXIGP7VMeU89KAt4SmQ1WB7PIo\nHpyP0aGLO/FSiFKu/Jp+rXE2yqo02T3nxqu4xctbL+JScXpE/InYokcqfrcqAvAp\n3zk2lgzzP7QIix0mwFv5PN0F2sCjHpucetqXeQl7snLUs/VTf4A96Th/2Ut/aSdE\nP99iY+gbdUI1if1EoRC95z53mDimTIvJXnG8FIUa6eR1bsosdHEP8zxJW2LV/iah\n3p3at32xgzyGCIeqC3ad7Ynv19EFTGqMMk+bl02RJ2TcvW0BjUEiKvI8g8zJfuM=\n=p6EV\n-----END PGP SIGNATURE-----\n",
        "payload": "tree eea1f8de390ac9f9adadbaecfc367ec5bacff33e\nparent 4f1260bc044f731c9b5c038a747d3af0ae5c7241\nparent 3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd\nauthor Georg Gadinger <nilsding@nilsding.org> 1657366829 +0200\ncommitter GitHub <noreply@github.com> 1657366829 +0200\n\nMerge pull request #507 from Retrospring/depperterbot\n\nDependabot merge branch"
      }
    },
    "url": "https://api.github.com/repos/Retrospring/retrospring/commits/b6c42264a6652674deb66e7dd9b95a784fea8e40",
    "html_url": "https://github.com/Retrospring/retrospring/commit/b6c42264a6652674deb66e7dd9b95a784fea8e40",
    "comments_url": "https://api.github.com/repos/Retrospring/retrospring/commits/b6c42264a6652674deb66e7dd9b95a784fea8e40/comments",
    "author": {
      "login": "nilsding",
[...]

GitHub returns nicely formatted JSON. For servers that don’t, it can be helpful to pipe the response through rj to pretty-print it. The simplest usage of rj is to not pass any expressions to it at all; rj takes the input and prints it in a formatted way.

curl 'https://api.github.com/repos/Retrospring/retrospring/commits?per_page=5' | rj
Show result
[
  {
    "sha": "b6c42264a6652674deb66e7dd9b95a784fea8e40",
    "node_id": "C_kwDOAk1rYNoAKGI2YzQyMjY0YTY2NTI2NzRkZWI2NmU3ZGQ5Yjk1YTc4NGZlYThlNDA",
    "commit": {
      "author": {
        "name": "Georg Gadinger",
        "email": "nilsding@nilsding.org",
        "date": "2022-07-09T11:40:29Z"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "date": "2022-07-09T11:40:29Z"
      },
      "message": "Merge pull request #507 from Retrospring/depperterbot\n\nDependabot merge branch",
      "tree": {
        "sha": "eea1f8de390ac9f9adadbaecfc367ec5bacff33e",
        "url": "https://api.github.com/repos/Retrospring/retrospring/git/trees/eea1f8de390ac9f9adadbaecfc367ec5bacff33e"
      },
      "url": "https://api.github.com/repos/Retrospring/retrospring/git/commits/b6c42264a6652674deb66e7dd9b95a784fea8e40",
      "comment_count": 0,
      "verification": {
        "verified": true,
        "reason": "valid",
        "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJiyWktCRBK7hj4Ov3rIwAAhYYIAIAOf3QibMAo38jRRSqmFgf0\nTAwJmW01Vkp9dAcQgUvMAwopyN+w4pFt3JgXh4hXIGP7VMeU89KAt4SmQ1WB7PIo\nHpyP0aGLO/FSiFKu/Jp+rXE2yqo02T3nxqu4xctbL+JScXpE/InYokcqfrcqAvAp\n3zk2lgzzP7QIix0mwFv5PN0F2sCjHpucetqXeQl7snLUs/VTf4A96Th/2Ut/aSdE\nP99iY+gbdUI1if1EoRC95z53mDimTIvJXnG8FIUa6eR1bsosdHEP8zxJW2LV/iah\n3p3at32xgzyGCIeqC3ad7Ynv19EFTGqMMk+bl02RJ2TcvW0BjUEiKvI8g8zJfuM=\n=p6EV\n-----END PGP SIGNATURE-----\n",
        "payload": "tree eea1f8de390ac9f9adadbaecfc367ec5bacff33e\nparent 4f1260bc044f731c9b5c038a747d3af0ae5c7241\nparent 3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd\nauthor Georg Gadinger <nilsding@nilsding.org> 1657366829 +0200\ncommitter GitHub <noreply@github.com> 1657366829 +0200\n\nMerge pull request #507 from Retrospring/depperterbot\n\nDependabot merge branch"
      }
    },
    "url": "https://api.github.com/repos/Retrospring/retrospring/commits/b6c42264a6652674deb66e7dd9b95a784fea8e40",
    "html_url": "https://github.com/Retrospring/retrospring/commit/b6c42264a6652674deb66e7dd9b95a784fea8e40",
    "comments_url": "https://api.github.com/repos/Retrospring/retrospring/commits/b6c42264a6652674deb66e7dd9b95a784fea8e40/comments",
    "author": {
      "login": "nilsding",
[...]

We can use rj to extract just the first commit. You can do that in many ways, for example by using the Enumerable#first method, or in this case using Array#[].

curl 'https://api.github.com/repos/Retrospring/retrospring/commits?per_page=5' | rj '.first'

# this works too:
curl 'https://api.github.com/repos/Retrospring/retrospring/commits?per_page=5' | rj '[0]'
Show result
{
  "sha": "b6c42264a6652674deb66e7dd9b95a784fea8e40",
  "node_id": "C_kwDOAk1rYNoAKGI2YzQyMjY0YTY2NTI2NzRkZWI2NmU3ZGQ5Yjk1YTc4NGZlYThlNDA",
  "commit": {
    "author": {
      "name": "Georg Gadinger",
      "email": "nilsding@nilsding.org",
      "date": "2022-07-09T11:40:29Z"
    },
    "committer": {
      "name": "GitHub",
      "email": "noreply@github.com",
      "date": "2022-07-09T11:40:29Z"
    },
    "message": "Merge pull request #507 from Retrospring/depperterbot\n\nDependabot merge branch",
    "tree": {
      "sha": "eea1f8de390ac9f9adadbaecfc367ec5bacff33e",
      "url": "https://api.github.com/repos/Retrospring/retrospring/git/trees/eea1f8de390ac9f9adadbaecfc367ec5bacff33e"
    },
    "url": "https://api.github.com/repos/Retrospring/retrospring/git/commits/b6c42264a6652674deb66e7dd9b95a784fea8e40",
    "comment_count": 0,
    "verification": {
      "verified": true,
      "reason": "valid",
      "signature": "-----BEGIN PGP SIGNATURE-----\n\nwsBcBAABCAAQBQJiyWktCRBK7hj4Ov3rIwAAhYYIAIAOf3QibMAo38jRRSqmFgf0\nTAwJmW01Vkp9dAcQgUvMAwopyN+w4pFt3JgXh4hXIGP7VMeU89KAt4SmQ1WB7PIo\nHpyP0aGLO/FSiFKu/Jp+rXE2yqo02T3nxqu4xctbL+JScXpE/InYokcqfrcqAvAp\n3zk2lgzzP7QIix0mwFv5PN0F2sCjHpucetqXeQl7snLUs/VTf4A96Th/2Ut/aSdE\nP99iY+gbdUI1if1EoRC95z53mDimTIvJXnG8FIUa6eR1bsosdHEP8zxJW2LV/iah\n3p3at32xgzyGCIeqC3ad7Ynv19EFTGqMMk+bl02RJ2TcvW0BjUEiKvI8g8zJfuM=\n=p6EV\n-----END PGP SIGNATURE-----\n",
      "payload": "tree eea1f8de390ac9f9adadbaecfc367ec5bacff33e\nparent 4f1260bc044f731c9b5c038a747d3af0ae5c7241\nparent 3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd\nauthor Georg Gadinger <nilsding@nilsding.org> 1657366829 +0200\ncommitter GitHub <noreply@github.com> 1657366829 +0200\n\nMerge pull request #507 from Retrospring/depperterbot\n\nDependabot merge branch"
    }
  },
  "url": "https://api.github.com/repos/Retrospring/retrospring/commits/b6c42264a6652674deb66e7dd9b95a784fea8e40",
  "html_url": "https://github.com/Retrospring/retrospring/commit/b6c42264a6652674deb66e7dd9b95a784fea8e40",
  "comments_url": "https://api.github.com/repos/Retrospring/retrospring/commits/b6c42264a6652674deb66e7dd9b95a784fea8e40/comments",
  "author": {
    "login": "nilsding",
    "id": 1809170,
    "node_id": "MDQ6VXNlcjE4MDkxNzA=",
    "avatar_url": "https://avatars.githubusercontent.com/u/1809170?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/nilsding",
    "html_url": "https://github.com/nilsding",
    "followers_url": "https://api.github.com/users/nilsding/followers",
    "following_url": "https://api.github.com/users/nilsding/following{/other_user}",
    "gists_url": "https://api.github.com/users/nilsding/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/nilsding/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/nilsding/subscriptions",
    "organizations_url": "https://api.github.com/users/nilsding/orgs",
    "repos_url": "https://api.github.com/users/nilsding/repos",
    "events_url": "https://api.github.com/users/nilsding/events{/privacy}",
    "received_events_url": "https://api.github.com/users/nilsding/received_events",
    "type": "User",
    "site_admin": false
  },
  "committer": {
    "login": "web-flow",
    "id": 19864447,
    "node_id": "MDQ6VXNlcjE5ODY0NDQ3",
    "avatar_url": "https://avatars.githubusercontent.com/u/19864447?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/web-flow",
    "html_url": "https://github.com/web-flow",
    "followers_url": "https://api.github.com/users/web-flow/followers",
    "following_url": "https://api.github.com/users/web-flow/following{/other_user}",
    "gists_url": "https://api.github.com/users/web-flow/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/web-flow/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/web-flow/subscriptions",
    "organizations_url": "https://api.github.com/users/web-flow/orgs",
    "repos_url": "https://api.github.com/users/web-flow/repos",
    "events_url": "https://api.github.com/users/web-flow/events{/privacy}",
    "received_events_url": "https://api.github.com/users/web-flow/received_events",
    "type": "User",
    "site_admin": false
  },
  "parents": [
    {
      "sha": "4f1260bc044f731c9b5c038a747d3af0ae5c7241",
      "url": "https://api.github.com/repos/Retrospring/retrospring/commits/4f1260bc044f731c9b5c038a747d3af0ae5c7241",
      "html_url": "https://github.com/Retrospring/retrospring/commit/4f1260bc044f731c9b5c038a747d3af0ae5c7241"
    },
    {
      "sha": "3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd",
      "url": "https://api.github.com/repos/Retrospring/retrospring/commits/3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd",
      "html_url": "https://github.com/Retrospring/retrospring/commit/3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd"
    }
  ]
}

For the rest of the examples, I’ll leave out the curl command - it’s not going to change.

There’s a lot of info we don’t care about there, so we’ll restrict it down to the most interesting fields.

rj '.first' '{ message: item.dig("commit", "message"), name: item.dig("commit", "committer", "name") }'
Show result
{
  "message": "Merge pull request #507 from Retrospring/depperterbot\n\nDependabot merge branch",
  "name": "GitHub"
}

Chaining expressions in rj is done by passing another expression after one. In case you don’t want to chain another method on the previous result, but return another one entirely you can do that too. To access the previous result you can use the item variable.

I also used the used the Hash#dig method to lazily access nested attributes. However since expressions are just Ruby code you can even do the following:

# Here I assigned the contents of the `commit` field to a temporary variable
# which I then use in the resulting hash.  The result is the same as before.
rj '.first' 'commit = item["commit"]; { message: commit["message"], name: commit.dig("committer", "name") }'

But I’m not done quite yet, I would like to only get the first line of a commit message. This can be achieve by using the String#split method along with one of the methods we used earlier to get the first element of an array:

rj '.first' '{ message: item.dig("commit", "message").split("\n").first, name: item.dig("commit", "committer", "name") }'
Show result
{
  "message": "Merge pull request #507 from Retrospring/depperterbot",
  "name": "GitHub"
}

That’s more like it. Let’s apply that to all our commits:

rj '.map { |x| { message: x.dig("commit", "message").split("\n").first, name: x.dig("commit", "committer", "name") } }'
Show result
[
  {
    "message": "Merge pull request #507 from Retrospring/depperterbot",
    "name": "GitHub"
  },
  {
    "message": "Bump bootstrap_form from 5.0.0 to 5.1.0",
    "name": "Andreas Nedbal"
  },
  {
    "message": "remove fix_* tasks as we ensured we won't need them anymore a long time ago",
    "name": "Georg Gadinger"
  },
  {
    "message": "Remove unneeded Rake tasks",
    "name": "Georg Gadinger"
  },
  {
    "message": "Bump actions/setup-node from 1 to 3",
    "name": "Georg Gadinger"
  }
]

The Enumerable#map method (which is also accessible as Enumerable#collect) applies the value returned of the block passed to it. A Ruby block is essentially an anonymous function that does something based on the parameters given (in the example above I named the parameter x) and returns a new value. .map calls this function on each element of the array (or object!) and returns a new array with the resulting values.

Next, let’s try getting the URLs of the parent commits out of the API results as well. In each commit, the GitHub API includes information about “parent” commits. There can be one or many.

"parents": [
  {
    "sha": "4f1260bc044f731c9b5c038a747d3af0ae5c7241",
    "url": "https://api.github.com/repos/Retrospring/retrospring/commits/4f1260bc044f731c9b5c038a747d3af0ae5c7241",
    "html_url": "https://github.com/Retrospring/retrospring/commit/4f1260bc044f731c9b5c038a747d3af0ae5c7241"
  },
  {
    "sha": "3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd",
    "url": "https://api.github.com/repos/Retrospring/retrospring/commits/3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd",
    "html_url": "https://github.com/Retrospring/retrospring/commit/3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd"
  }
]

We want to pull out all of the "html_url" fields inside that array of parent commits and make a simple list of strings to go along with the "message" and "author" fields we already have.

rj '.map { |x| { message: x.dig("commit", "message").split("\n").first, name: x.dig("commit", "committer", "name"), parents: x["parents"].map { |parent| parent["html_url"] } } }'
Show result
[
  {
    "message": "Merge pull request #507 from Retrospring/depperterbot",
    "name": "GitHub",
    "parents": [
      "https://github.com/Retrospring/retrospring/commit/4f1260bc044f731c9b5c038a747d3af0ae5c7241",
      "https://github.com/Retrospring/retrospring/commit/3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd"
    ]
  },
  {
    "message": "Bump bootstrap_form from 5.0.0 to 5.1.0",
    "name": "Andreas Nedbal",
    "parents": [
      "https://github.com/Retrospring/retrospring/commit/84d77704b11a19c17b370808f9ccab8ac384b4e2"
    ]
  },
  {
    "message": "remove fix_* tasks as we ensured we won't need them anymore a long time ago",
    "name": "Georg Gadinger",
    "parents": [
      "https://github.com/Retrospring/retrospring/commit/31e9d7ac8059fbf7148ee49da1282ca6a93a536a"
    ]
  },
  {
    "message": "Remove unneeded Rake tasks",
    "name": "Georg Gadinger",
    "parents": [
      "https://github.com/Retrospring/retrospring/commit/5155b6dee761ba538e8f8c2b07773abc672dbaa2"
    ]
  },
  {
    "message": "Bump actions/setup-node from 1 to 3",
    "name": "Georg Gadinger",
    "parents": [
      "https://github.com/Retrospring/retrospring/commit/18386e88ed7379af9fbc0260b7304491a61d5ae8"
    ]
  }
]

Here we’re making a new object as before, but this time the parents field is being set to x["parents"].map { |parent| parent["html_url"] }, which collects all of the parent commit URLs defined in the parents object.

Finally, we can tell rj to output our transformed object as a Ruby hash by setting -o ruby.

rj -o ruby '.map { |x| { message: x.dig("commit", "message").split("\n").first, name: x.dig("commit", "committer", "name"), parents: x["parents"].map { |parent| parent["html_url"] } } }'
Show result
[
  {
    :message => "Merge pull request #507 from Retrospring/depperterbot",
    :name => "GitHub",
    :parents => [
      "https://github.com/Retrospring/retrospring/commit/4f1260bc044f731c9b5c038a747d3af0ae5c7241",
      "https://github.com/Retrospring/retrospring/commit/3e6eb346c45b66ece3c8c0c32ef2d797d52b1cfd",
    ],
  },
  {
    :message => "Bump bootstrap_form from 5.0.0 to 5.1.0",
    :name => "Andreas Nedbal",
    :parents => [
      "https://github.com/Retrospring/retrospring/commit/84d77704b11a19c17b370808f9ccab8ac384b4e2",
    ],
  },
  {
    :message => "remove fix_* tasks as we ensured we won't need them anymore a long time ago",
    :name => "Georg Gadinger",
    :parents => [
      "https://github.com/Retrospring/retrospring/commit/31e9d7ac8059fbf7148ee49da1282ca6a93a536a",
    ],
  },
  {
    :message => "Remove unneeded Rake tasks",
    :name => "Georg Gadinger",
    :parents => [
      "https://github.com/Retrospring/retrospring/commit/5155b6dee761ba538e8f8c2b07773abc672dbaa2",
    ],
  },
  {
    :message => "Bump actions/setup-node from 1 to 3",
    :name => "Georg Gadinger",
    :parents => [
      "https://github.com/Retrospring/retrospring/commit/18386e88ed7379af9fbc0260b7304491a61d5ae8",
    ],
  },
]