JSONファイルを扱うCUIツール「jq」を使ってみる - 土日の勉強ノート

土日の勉強ノート

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

JSONファイルを扱うCUIツール「jq」を使ってみる

前回 は、JSON ファイルを扱う方法をまとめました。

今回は、JSONファイルを扱うCUIツール「jq」について紹介します。

はじめに

前回の記事です。

daisuke20240310.hatenablog.com

e-Stat からダウンロードした「令和2年国勢調査の男女別人口の JSONファイル」を使います。

jqのインストール

「jq」のインストール方法として、Ubuntu 22.04 の場合と、Windows10 の Git BASH で使う場合の2通りを紹介します。

Ubuntu 22.04

とても簡単にインストールできます。

$ sudo apt install jq

Windows10のGit BASHでjqを使えるようにする

まず、Windows10 用の「jq」をダウンロードします。

「jq」の GitHub です。

github.com

リリースページ から、最新の Windows 用の exe(jq-win64.exe)をダウンロードします。

隠れてるかもしれないので、「Show all 29 assets」をクリックして、全て表示して、ダウンロードします。

適当な場所に置いて、コマンドプロンプトからも使えると思いますが、今回は、Git BASH で使えるように、ファイルを配置します。

Git BASH は、Git for Windows をインストールすると一緒に入るコンソールです。

Git for Windows をインストールしたフォルダ「C:\Program Files\Git」の下に「usr/bin」ディレクトリがあるので、そこに jq-win64.exe をコピーして、jq.exe にリネームしておきます。

Git BASHにインストール
Git BASHにインストール

Git BASH から使ってみます。

Git BASHでjqの動作確認
Git BASHでjqの動作確認

無事使えるようになりました。

jqの使い方

公式のドキュメントです。

jqlang.github.io

「jq」の基本的な使い方は、以下の2通りで、通常のコマンドのように、「jq フィルタ JSONファイル」の形で実行するか、パイプでつないでフィルタとして使うかです。

後者は、「.」を省略することが出来るようです。ここでは、以降は、後者の使い方とします。

$ jq . getStatsData.json
$ cat getStatsData.json | jq .

ちなみに、「.」は、ドキュメントによると、何もしないフィルタという感じで、受け取ったものを全て出力するという感じです。

大きいファイルを扱う場合は、less コマンドなどを入れておいた方がいいですね。

$ cat getStatsData.json | jq | less

jqの基本的な使い方

e-Stat のデータは、最初にしては難しいので、簡単なデータを用意しました。

{
  "ka0": {
    "ka1": "va1",
    "kb1": "vb1"
  },
  "kb0": [
    { "kc1": "vc1" },
    { "kd1": "vd1" }
  ]
}

sample1.json として、保存しておきます。

まずは、何もしないフィルタを使います。

$ cat sample1.json | jq .
{
  "ka0": {
    "ka1": "va1",
    "kb1": "vb1"
  },
  "kb0": [
    {
      "kc1": "vc1"
    },
    {
      "kd1": "vd1"
    }
  ]
}

少し整形されて出力されました。

JSON ファイルは、基本的に、キーと値のオブジェクト(ハッシュとも言う)の形式と、配列の形式しかありません。

この sample1.json は、最上位階層がオブジェクト形式になっています。

オブジェクトのキーを全て表示する方法です。配列になって出力されました。

$ cat sample1.json | jq keys
[
  "ka0",
  "kb0"
]

先頭のキーだけを選択し、その後、その中のキーを選択してみます。「.」に続いて、キーを続けます。値を取り出すことが出来ています。

$ cat sample1.json | jq .ka0
{
  "ka1": "va1",
  "kb1": "vb1"
}
$ cat sample1.json | jq .ka0.ka1
"va1"

続いて、配列について扱い方です。まず、2番目のキーを指定して配列を取り出し、その後、配列の中身を取り出します。さらに、配列の要素の2番目を指定してみます。

$ cat sample1.json | jq .kb0
[
  {
    "kc1": "vc1"
  },
  {
    "kd1": "vd1"
  }
]
$ cat sample1.json | jq .kb0[]
{
  "kc1": "vc1"
}
{
  "kd1": "vd1"
}
$ cat sample1.json | jq .kb0[1]
{
  "kd1": "vd1"
}

基本的な扱い方は以上になります。

jqで複雑なデータを扱う

では、e-Stat のデータを扱っていきます。

まずは、オブジェクト形式と想定して、キー一覧を出します。キーが1つだったので、次の階層のキー一覧を出します。

さらに、GUI ツールで見て "STATISTICAL_DATA" が一番大きいデータと分かっているので、そこのキー一覧を出します。

GUI ツールと見え方が変わるとややこしいので、ソートしないキー一覧も使っていきます。

$ cat getStatsData.json | jq keys
[
  "GET_STATS_DATA"
]
$  cat getStatsData.json | jq .GET_STATS_DATA | jq keys_unsorted
[
  "RESULT",
  "PARAMETER",
  "STATISTICAL_DATA"
]
$  cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq keys_unsorted
[
  "RESULT_INF",
  "TABLE_INF",
  "CLASS_INF",
  "DATA_INF"
]

大きい配列(CLASS)を見てみます。配列やオブジェクトの個数を調べるには、length を使います。

$ cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq .CLASS_INF.CLASS_OBJ[2] | jq .CLASS | jq length
4086

もう1つの大きい配列(DATA)を見てみます。

$ cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq .DATA_INF.VALUE | jq length
12258

CLASS の方のデータの先頭の3つです。

$ cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq .CLASS_INF.CLASS_OBJ[2] | jq .CLASS[:3]
[
  {
    "@code": "00000",
    "@name": "全国",
    "@level": "1"
  },
  {
    "@code": "01000",
    "@name": "北海道",
    "@level": "2",
    "@parentCode": "00000"
  },
  {
    "@code": "01100",
    "@name": "札幌市",
    "@level": "4",
    "@parentCode": "01000"
  }
]

jqの組み込み関数を使う

map(f) 関数を使ってみます。f は任意のフィルタです。入力のオブジェクト、または、配列に対して、まとめてフィルタを適用することが出来ます。

@name だけ抜き出して、見てみます。最後の「.CLASS」の出力は、4086個の配列です。

$ cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq .CLASS_INF.CLASS_OBJ[2] | jq .CLASS | jq 'map(."@name")'
[
  "全国",
  "北海道",
  "札幌市",
  "札幌市中央区",
  "札幌市北区",
  "札幌市東区",
  "札幌市白石区",
  "札幌市豊平区",
  "札幌市南区",
  "札幌市西区",
  "札幌市厚別区",
  "札幌市手稲区",
  "札幌市清田区",
  "函館市",
(省略)

次は、uniqe 関数を使ってみます。

DATA の方の先頭3つです。

$ cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq .DATA_INF.VALUE[:3]
[
  {
    "@tab": "2020_01",
    "@cat01": "0",
    "@area": "00000",
    "@time": "2020000000",
    "@unit": "",
    "$": "126146099"
  },
  {
    "@tab": "2020_01",
    "@cat01": "0",
    "@area": "01000",
    "@time": "2020000000",
    "@unit": "",
    "$": "5224614"
  },
  {
    "@tab": "2020_01",
    "@cat01": "0",
    "@area": "01100",
    "@time": "2020000000",
    "@unit": "",
    "$": "1973395"
  }
]

"@tab" が全て同じなのかを確認したいときなどに、unique 関数を使います。

$ cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq .DATA_INF.VALUE | jq 'map(."@tab")' | jq unique
[
  "2020_01"
]

全て同じ値だったようです。

次は、select 関数を使ってみます。条件にマッチする項目だけが表示されます。

人口が1億以上のものを求めます。

$ cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq .DATA_INF.VALUE | jq 'map(."$")' | jq .[] | jq 'tonumber' | jq 'select(. > 100000000)'
jq: error (at <stdin>:725): Invalid numeric literal at EOF at line 1, column 1 (while parsing '-')
jq: error (at <stdin>:4811): Invalid numeric literal at EOF at line 1, column 1 (while parsing '-')
jq: error (at <stdin>:8897): Invalid numeric literal at EOF at line 1, column 1 (while parsing '-')
126146099

いくつかエラーが出ています。調べてみると、人口のところが - になってるところが3か所ありました。ちょっと強引ですが、- を取り除いておきます。

$ cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq .DATA_INF.VALUE | jq 'map(."$")' | jq .[] | jq 'select(. != "-")' | jq 'tonumber' | jq 'select(. > 100000000)'
126146099

1つしかなかったので、1千万人以上に変更します。

$ cat getStatsData.json | jq .GET_STATS_DATA | jq .STATISTICAL_DATA | jq .DATA_INF.VALUE | jq 'map(."$")' | jq .[] | jq 'select(. != "-")' | jq 'tonumber' | jq 'select(. > 10000000)'
126146099
14047594
61349581
64796518

4つになりました。

おわりに

今回は、CUI で JSON ファイルを扱う方法として、「jq」コマンドを紹介しました。

ちょっと難しいコマンドなので、もう少し使い込んでいきたいと思います。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。