jqで少し複雑なjsonを検索+ソートする
以前の記事で、jqを使って小さな困りごとを解決したことを書いたが、あれから実際にjqをいろいろな場面で使い始めている。とはいえ最初はけっこう試行錯誤したので、実際に使ったコマンドを忘れないように記録しておきたい。
jqとは
以前の記事でも書いたが、jqはJSONデータに特化したsedコマンドのようなもので、JSONの特定のキーの値を使った検索・ソート・置換などが簡単にできる。(以前同じようことをsedやawkでやろうとしたことがあるが、かなり手間がかかったのでおすすめはしない)
JSONで吐かれる大量の生データ・ログデータを集計したいときなどに非常に重宝する。
今回はサンプルとして以下のようなJSONファイルを作ってみた。1行ごとにJSONが1つ吐き出される形式だ。
# test.json {"x":"hoge","y":"foo","s":{"a":true,"timestamp":"1557626945"}} {"x":"fuga","y":"bar","s":{"a":false,"timestamp":"1557626721"}} {"x":"hoge","y":"bar","s":{"a":true,"timestamp":"1557625712"}} {"x":"fuga","y":"foo","s":{"a":false,"timestamp":"1557626978"}} {"x":"hoge","y":"bar","s":{"a":true,"timestamp":"1557624928"}}
特定の値で検索・置換する
基本的に、jqを使うときは操作の基準となるキーを指定すれば良い。例えば、上のファイルでyの値がbarである行だけを取り出すときは以下のようになる。
$ jq -c '. | select(.y == "bar")' test.json {"x":"fuga","y":"bar","s":{"a":false,"timestamp":"1557626721"}} {"x":"hoge","y":"bar","s":{"a":true,"timestamp":"1557625712"}} {"x":"hoge","y":"bar","s":{"a":true,"timestamp":"1557624928"}}
入れ子の場合も、感覚的に指定できる。以下はaがfalseのもののみを取り出している。boolean型などもよしなに変換してくれるようだ。
$ jq -c '. | select(.s.a == false)' test.json {"x":"fuga","y":"bar","s":{"a":false,"timestamp":"1557626721"}} {"x":"fuga","y":"foo","s":{"a":false,"timestamp":"1557626978"}}
値を置き換える場合、新しい値を指定するだけでよい。
$ jq -c '. | select(.s.a == false) | .s.a = true' test.json {"x":"fuga","y":"bar","s":{"a":true,"timestamp":"1557626721"}} {"x":"fuga","y":"foo","s":{"a":true,"timestamp":"1557626978"}}
特定の値でソートする
ソートする場合、やり方が少し複雑になる。jqでは sort_by
を使うことで特定のキーの値でソートできるのだが、普通にやろうとするとエラーになる。
$ jq -c '. | sort_by(.s.timestamp)' test.json jq: error (at test.json:1): Cannot index string with string "s" jq: error (at test.json:2): Cannot index string with string "s" jq: error (at test.json:3): Cannot index string with string "s" jq: error (at test.json:4): Cannot index string with string "s" jq: error (at test.json:5): Cannot index string with string "s"
これは、sort_by
を使うときの入力値は配列でなければいけないため。
そこで、ファイルを指定する際に --slurp
オプションをつけることで、入力値を配列にでき、ソートが可能になる。その後に.[]
と指定すれば、今度は配列を外すことができ、出力を1行ごとに戻すことができる。
$ jq -c '. | sort_by(.s.timestamp) |.[]' --slurp test.json {"x":"hoge","y":"bar","s":{"a":true,"timestamp":"1557624928"}} {"x":"hoge","y":"bar","s":{"a":true,"timestamp":"1557625712"}} {"x":"fuga","y":"bar","s":{"a":false,"timestamp":"1557626721"}} {"x":"hoge","y":"foo","s":{"a":true,"timestamp":"1557626945"}} {"x":"fuga","y":"foo","s":{"a":false,"timestamp":"1557626978"}}
逆に、selectを配列の状態で使うことはできない。
$ jq -c '. | select(.y == "bar") |.[]' --slurp test.json jq: error (at test.json:5): Cannot index array with string "y"
検索とソートを一緒にやりたいときは、まずソートを行ってから、配列を外して検索すると良いだろう。
$ jq -c '. | sort_by(.s.timestamp) |.[] | select(.y=="bar")' --slurp test.json {"x":"hoge","y":"bar","s":{"a":true,"timestamp":"1557624928"}} {"x":"hoge","y":"bar","s":{"a":true,"timestamp":"1557625712"}} {"x":"fuga","y":"bar","s":{"a":false,"timestamp":"1557626721"}}
これらを組み合わせることで、JSONを簡単に加工することができる。出力を確認するテスト等の時に大いに役立つだろう。