jqコマンドを使ってJSONをいい感じに整える

# はじめに
APIのレスポンスデータをJSON形式で扱う際に、そのままでは見づらいという問題がありました。何かいい方法はないかと探したところ、**jq**というコマンドラインツールを見つけたので紹介します。

# jqコマンド
jqは、JSONデータを整形、フィルタリング、変換するための強力なツールで、データの操作を簡単にしてくれます。今回は試しにUSGS（アメリカ地質調査所）の地震情報APIを使って、レスポンスデータをいい感じに整形していきます。
エンドポイント：https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson

## jqコマンドなしの場合
以下は単純にcurlコマンドのみでAPIを叩いた場合です。
```terminal
curl -s "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson"
```
ぱっと見自分が欲しい値を見つけるのが大変ですよね。
```terminal
{"type":"FeatureCollection","metadata":{"generated":1725522255000,"url":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson","title":"USGS Magnitude 4.5+ Earthquakes, Past Day","status":200,"api":"1.10.3","count":8},"features":[{"type":"Feature","properties":{"mag":4.9,"place":"south of Panama","time":1725514018720,"updated":1725517438040,"tz":null,"url":"https://earthquake.usgs.gov/earthquakes/eventpage/us6000npsw","detail":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us6000npsw.geojson","felt":null,"cdi":null,"mmi":null,"alert":null,"status":"reviewed","tsunami":0,"sig":369,"net":"us","code":"6000npsw","ids":",us6000npsw,","sources":",us,","types":",origin,phase-data,","nst":52,"dmin":3.731,"rms":1.08,"gap":141,"magType":"mww","type":"earthquake","title":"M 4.9 - south of Panama"},"geometry":{"type":"Point","coordinates":[-82.756,4.6982,10]},"id":"us6000npsw"},
{"type":"Feature","properties":{"mag":5.1,"place":"71 km SSE of Modisi, Indonesia","time":1725510691387,"updated":1725511749040,"tz":null,"url":"https://earthquake.usgs.gov/earthquakes/eventpage/us6000npsr","detail":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us6000npsr.geojson","felt":null,"cdi":null,"mmi":null,"alert":null,"status":"reviewed","tsunami":0,"sig":400,"net":"us","code":"6000npsr","ids":",us6000npsr,","sources":",us,","types":",origin,phase-data,","nst":72,"dmin":2.959,"rms":1.13,"gap":56,"magType":"mb","type":"earthquake","title":"M 5.1 - 71 km SSE of Modisi, Indonesia"},"geometry":{"type":"Point","coordinates":[124.565,-0.1858,64.088]},"id":"us6000npsr"},
{"type":"Feature","properties":{"mag":4.5,"place":"270 km SSW of Severo-Kuril’sk, Russia","time":1725505023932,"updated":1725507179040,"tz":null,"url":"https://earthquake.usgs.gov/earthquakes/eventpage/us6000npsh","detail":"https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us6000npsh.geojson","felt":null,"cdi":null,"mmi":null,"alert":null,"status":"reviewed","tsunami":0,"sig":312,"net":"us","code":"6000npsh","ids":",us6000npsh,","sources":",us,","types":",origin,phase-data,","nst":59,"dmin":5.047,"rms":0.57,"gap":128,"magType":"mb","type":"earthquake","title":"M 4.5 - 270 km SSW of Severo-Kuril’sk, Russia"},"geometry":{"type":"Point","coordinates":[154.4504,48.5055,65.355]},"id":"us6000npsh"},
⋮
```
## jqのインストール
```terminal
brew install jq
```

## jqの基本的な使い方
jqをパイプで繋ぎます(jq .はjqでも可)。
```terminal
curl -s "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson" | jq .
```
レスポンスは以下の通り。
```terminal
{
  "type": "FeatureCollection",
  "metadata": {
    "generated": 1725522795000,
    "url": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson",
    "title": "USGS Magnitude 4.5+ Earthquakes, Past Day",
    "status": 200,
    "api": "1.10.3",
    "count": 8
  },
  "features": [
    {
      "type": "Feature",
      "properties": {
        "mag": 4.9,
        "place": "south of Panama",
        "time": 1725514018720,
        "updated": 1725517438040,
        "tz": null,
        "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us6000npsw",
        "detail": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us6000npsw.geojson",
        "felt": null,
        "cdi": null,
        "mmi": null,
        "alert": null,
        "status": "reviewed",
        "tsunami": 0,
        "sig": 369,
        "net": "us",
        "code": "6000npsw",
        "ids": ",us6000npsw,",
        "sources": ",us,",
        "types": ",origin,phase-data,",
        "nst": 52,
        "dmin": 3.731,
        "rms": 1.08,
        "gap": 141,
        "magType": "mww",
        "type": "earthquake",
        "title": "M 4.9 - south of Panama"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          -82.756,
          4.6982,
          10
        ]
      },
      "id": "us6000npsw"
    },
    {
      "type": "Feature",
      "properties": {
        "mag": 5.1,
        "place": "71 km SSE of Modisi, Indonesia",
        "time": 1725510691387,
        "updated": 1725511749040,
        "tz": null,
        "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us6000npsr",
        "detail": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us6000npsr.geojson",
        "felt": null,
        "cdi": null,
        "mmi": null,
        "alert": null,
        "status": "reviewed",
        "tsunami": 0,
        "sig": 400,
        "net": "us",
        "code": "6000npsr",
        "ids": ",us6000npsr,",
        "sources": ",us,",
        "types": ",origin,phase-data,",
        "nst": 72,
        "dmin": 2.959,
        "rms": 1.13,
        "gap": 56,
        "magType": "mb",
        "type": "earthquake",
        "title": "M 5.1 - 71 km SSE of Modisi, Indonesia"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          124.565,
          -0.1858,
          64.088
        ]
      },
      "id": "us6000npsr"
    },
    ⋮
```

## JSONデータのフィルタリング
jqを使って特定のフィールドを取り出すこともできます。以下の例ではマグニチュード(mag)と場所(place)を取り出します。
```terminal
curl -s "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson" | jq '.features[].properties | {mag: .mag, place: .place}'
```
features配列の各要素に対して、magとplaceフィールドを抽出します。
```terminal
{
  "mag": 4.9,
  "place": "south of Panama"
}
{
  "mag": 5.1,
  "place": "71 km SSE of Modisi, Indonesia"
}
{
  "mag": 4.5,
  "place": "270 km SSW of Severo-Kuril’sk, Russia"
}
⋮
```

## 高度なフィルタリング
jqには、条件付きフィルタリングや集計、再帰的なデータ操作なども可能です。

### 条件付きフィルタリング
以下の例では、マグニチュードが5以上の地震情報だけを抽出します。
```terminal
curl -s "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson" | jq '.features[].properties | select(.mag >= 5) | {mag: .mag, place: .place}'
```
```terminal
{
  "mag": 5.1,
  "place": "71 km SSE of Modisi, Indonesia"
}
{
  "mag": 6.2,
  "place": "66 km NNE of Angoram, Papua New Guinea"
}
{
  "mag": 5.3,
  "place": "11 km ESE of Lata, Solomon Islands"
}
⋮
```

### データの集計
以下の例では、地震のマグニチュードの平均値を計算することもできます。
```terminal
curl -s "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson" | jq '[.features[].properties.mag] | add / length'
```

```terminal
5.075
```

### 再帰的なデータ操作
再帰的にデータを探索も可能です。事前に条件を指定することで、それに一致するデータを抽出することもできます。例えば、ネストされたすべてのオブジェクトからmag（マグニチュード）フィールドを持つものを抽出します。
```terminal
curl -s "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson" | jq '.. | objects | select(has("mag"))'
```
このコマンドは、以下のように動作します：

1. 再帰的にすべての要素を探索
2. objectsで、JSONオブジェクトだけを選択
3. select(has("mag"))で、magフィールドを持つオブジェクトだけを抽出
```terminal
{
  "mag": 5,
  "place": "south of the Kermadec Islands",
  "time": 1725524717650,
  "updated": 1725526251040,
  "tz": null,
  "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us6000nptr",
  "detail": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us6000nptr.geojson",
  "felt": null,
  "cdi": null,
  "mmi": null,
  "alert": null,
  "status": "reviewed",
  "tsunami": 0,
  "sig": 385,
  "net": "us",
  "code": "6000nptr",
  "ids": ",us6000nptr,",
  "sources": ",us,",
  "types": ",origin,phase-data,",
  "nst": 36,
  "dmin": 4.165,
  "rms": 0.94,
  "gap": 124,
  "magType": "mb",
  "type": "earthquake",
  "title": "M 5.0 - south of the Kermadec Islands"
}
{
  "mag": 4.9,
  "place": "south of Panama",
  "time": 1725514018720,
  "updated": 1725517438040,
  "tz": null,
  "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us6000npsw",
  "detail": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us6000npsw.geojson",
  "felt": null,
  "cdi": null,
  "mmi": null,
  "alert": null,
  "status": "reviewed",
  "tsunami": 0,
  "sig": 369,
  "net": "us",
  "code": "6000npsw",
  "ids": ",us6000npsw,",
  "sources": ",us,",
  "types": ",origin,phase-data,",
  "nst": 52,
  "dmin": 3.731,
  "rms": 1.08,
  "gap": 141,
  "magType": "mww",
  "type": "earthquake",
  "title": "M 4.9 - south of Panama"
}
{
  "mag": 5.1,
  "place": "71 km SSE of Modisi, Indonesia",
  "time": 1725510691387,
  "updated": 1725511749040,
  "tz": null,
  "url": "https://earthquake.usgs.gov/earthquakes/eventpage/us6000npsr",
  "detail": "https://earthquake.usgs.gov/earthquakes/feed/v1.0/detail/us6000npsr.geojson",
  "felt": null,
  "cdi": null,
  "mmi": null,
  "alert": null,
  "status": "reviewed",
  "tsunami": 0,
  "sig": 400,
  "net": "us",
  "code": "6000npsr",
  "ids": ",us6000npsr,",
  "sources": ",us,",
  "types": ",origin,phase-data,",
  "nst": 72,
  "dmin": 2.959,
  "rms": 1.13,
  "gap": 56,
  "magType": "mb",
  "type": "earthquake",
  "title": "M 5.1 - 71 km SSE of Modisi, Indonesia"
}
⋮
```

## JSONデータからCSVへの変換
jqを使えばJSONデータをCSV形式に変換することも可能です。マグニチュードと場所を抽出してCSVに変換してみます。
```terminal
curl -s "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/4.5_day.geojson" | jq -r '.features[].properties | [.mag, .place] | @csv'
```
-rオプションは、jqの出力を生の文字列として表示するためのものです。
```terminal
4.9,"south of Panama"
5.1,"71 km SSE of Modisi, Indonesia"
4.5,"270 km SSW of Severo-Kuril’sk, Russia"
6.2,"66 km NNE of Angoram, Papua New Guinea"
⋮
```

# 最後に
jqは、JSONデータを扱う上で非常に便利なツールです。整形、フィルタリング、変換など、さまざまな操作を簡単に行うことができます。特に、APIのレスポンスを解析したり、大量のJSONデータを処理する際には、その威力を発揮します。ぜひ、jqを使ってJSONデータの操作を効率化してみてください。