コラム

2021.05.06

【テックコラム】 JSON に困ったときの特効薬 jq の紹介

●はじめに


データを扱う上で良く用いられるファイルフォーマットは CSV ですね。
CSVはカラム名と行列で構成される表形式データです。
業務で SQL を使用している人は、データが表形式で表現されている方が把握しやすいですね。

片や API などから取得できるデータ形式は JSON が一般的です。
JSON は Array、Hash が組み合わされた構造化データです。
そのままで Excel で読み込めることは殆ど無く、JSON は扱いにくいイメージを持たれている方が多いのではないでしょうか。

そのギャップを埋めるツールとして、jq を紹介します。

https://stedolan.github.io/jq/

本記事の対象バージョンは 1.6 です。

ここでは JSON から CSV や TSV といった表形式データを得る視点で、基本的な操作を解説して行きます。
※以下の操作は linux と Mac、Windows は PowerShell を想定しています
※jq のインストールやコマンドラインの使い方については割愛します

● jq の役割


jq では下記の要素を組み合わせることで、値の抽出、加工、出力を行います。

  1. 取得する値を特定する(Identity)
  2. 演算や関数を適用する(フィルタ)
  3. array に整形し @csv や @tsv で出力する

ここからは、それぞれのステップについて解説していきます。

1.取得する値を特定する(Identity)

JSON のどの値を参照するかを示します。 JSON では Array の場合はインデックス値、Hash の場合はキー名を使い、値を取得しますよね 。

jq では、その指定に Identity を使用します。

Hash Identity

.[<string>]

hash の key が <string> の値を参照します。 .<string> と省略できます。

記述例: .["foo"] または .foo 
echo '{"abc":123,"def":456}' | jq '.def'
456

Array Identity

.[<n>]

array のインデックスが <n> の要素を参照します。0 が要素の1番目になります。

インデックス値が単独の場合 .<n> と省略可能です。

記述例: .[2] または .2
echo '[10,8,6,4,2]'  | jq '.[1]'
8

インデックス値は : を用いて開始と終了の範囲を指定できます。終了のインデックス値は含みません。

記述例:.[3:5]  (インデックス 3 から 5 を取得)
echo '[10,8,6,4,2]' | jq '.[1:3]'
[
  8,
  6
]

また、マイナス値で後方からの番号を指定できます。

記述例: .[-1] (末尾の1番目、-0 ではないことに注意)

2. 演算や関数を適用する(フィルタ)

数値の四則演算や、文字列の加工、変換などを行います。

四則演算

echo '[10,8,6,4,2]'  | jq '.[1] * 10'
80

strftime: unixtime を UTC の指定書式に変換

echo '1619148896' | jq 'strftime("%Y-%m-%d %H:%M:%S")'
"2021-04-23 03:34:56"

strflocaltime: unixtime を 実行環境のタイムゾーンの指定書式に変換

echo '1619148896' | jq 'strflocaltime("%Y-%m-%d %H:%M:%S %Z")'
"2021-04-23 12:34:56 JST"

tostring: 数値から文字列へ変換

echo '[123, 456]' | jq '(.[0] | tostring)'
"124"

gsub: 正規表現にマッチしたものを置換する

echo '["abc", "asd"]' | jq '(.[0] | sub("^ab"; "qw"))'
"qwc"

上のように、Identityと組み合わせて使用する場合 | (パイプ)を使用します。

| は連続して使用することが出来ます。

また演算の範囲を括弧でくくります。

echo '[123, 456]' | jq '(.[0] | tostring), .[1]'
"123"
456

3. array に整形し @csv や @tsv で出力する

jq で csv や tsv で出力するには、事前に array を作成しておく必要があります。
Array を作成するには、フィルタを [] で括りカンマで列挙します。

また jq は標準では CSV 出力時にダブルクオーテーションを \ でエスケープします。これは不要な場合が多いので、-r オプションを付与しこれを抑制します。

フィルタの最後にフォーマットを指定します。
@csv でカンマ区切りの CSV、@tsv でタブ区切りの TSV です。

echo '[1619148896]' | jq -r '.[0] | [., strftime("%Y-%m-%d")] | @csv'
1619148896,"2021-04-23"

では次に、少し複雑な JSON を TSV に変換してみましょう。

AWS の CloudWatch Logs のログは、下記の message のように awc cli で取得すると、ログ部分が JSON 形式で且つ文字列に格納されていることがあります。(JSON 内 JSON)

{
  "events": [
  {
    "logStreamName": "app-firelens-14c51c7faa05cf73fc26c6f2d",
    "timestamp": 1619148896812,
    "message": "{\"container_id\":\"701e007072e942c68c8af6e\",\"container_name\":\"/ecs-app-task-def-24-app-e0e7fc0\",\"ecs_cluster\":\"arn:aws:ecs:ap-northeast-1:33930:cluster/app-cluster\",\"ecs_task_arn\":\"arn:aws:ecs:ap-northeast-1:38730:task/app-cluster/14caa05cf73fc26c6f2d\",\"ecs_task_definition\":\"dc-tag-app-task-def:24\",\"log\":\"[INFO] cron.update_data: download data file\",\"source\":\"stderr\"}",
    "ingestionTime": 1619148896812,
    "eventId": "360308328973451422123294722"
  }
  ]
}

この JSON から、
ログ日時(UTC),  message 内の コンテナID, log
をコンソールでざっくりと確認することを想定します。

  • コンソールで見やすくするため、CSV ではなく TSV で出力します。
  • コンテナIDは元のままでは長すぎるため、先頭 6 文字のみ出力します。
  • timestamp の値はミリ秒なので、秒へ変換し strftime で可読できるように変換します。
  • message 内から container_id や log を取得するには、元の値を fromjson フィルタにより json として評価します。
(.message | fromjson | .container_id, .log)
  • これらを [] で括り、@tsv フィルタを使用します。

コマンドラインと出力結果は以下のようになります。

cat sample_awslog.json | jq -r '.events[] | [(.timestamp / 1000 | strftime("%Y-%m-%d %H:%M:%S")), (."message" | fromjson | .container_id[0:6], .log)] | @tsv'
2021-04-23 03:34:56     701e00  [INFO] cron.update_data: download data file

これで見やすくなりましたね。

最後に


弊社では、これまで様々な API にてデータの取得や連携を行ってまいりました。「API を利用した自動化に興味がある」や「いま使っているシステムに API は用意されているが活用できていない」といったお悩みの支援も可能ですので、お気軽にお問い合わせください。

本データに関するお問い合わせは下記にて承ります。
株式会社DataCurrent
info@datacurrent.co.jp