jqのむだづかいー表整形篇

[JSON](https://datatracker.ietf.org/doc/html/rfc8259)処理のコマンドラインツールである[jq](https://stedolan.github.io/jq/)は、JSONテキストを1行でちゃくっと解析、変換するときに使うものです。単体では複雑なことは得手ではないので、そうしたときは`sed`や`awk`など他のユーティリティを併用するのが通例です。

とは言え、`jq`にも変数定義などプログラミング言語らしき機能が備わっているので、「やればできんじゃねぇ?」と野望を募らせてしまうこともあります。

ここでは、JSONオブジェクト値の表形式への整理を試みます。具体的には、次のような同一形式のオブジェクトの配列を受け取り、

```json
[
  {
    "name": "Penfolds Bin 389",
    "region": "South Austraria"
  },
  {
    "name": "Chateau Cantenac Brown",
    "region": "Margaux"
  },
  {
    "name": "Tenuta San Guido Le Difese",
    "region": "Bolgheri"
  }
]
```

オブジェクトの値で次のような表形式で出力します。

```
Penfolds Bin 389               South Austraria
Chateau Cantenac Brown         Margaux
Tenuta San Guido Le Difese     Bolgheri
```

以下、入力データはファイル`tabular-sample.json`に収容されているとします。


### コード

コード（フィルタファイル）は`tabular.jq`で、次の[Github](https://github.com/stoyosawa/jqDoc-public/tree/main/filters)から取得できます（短いのでコピペでも十分ですが）。

```https://github.com/stoyosawa/jqDoc-public/```

フィルタファイルの改行はUnixスタイルの`LF`だけでなければならないので、保存時に注意してください。Windows流に`CRLF`だと次のエラーが報告されます。

```
jq: error: syntax error, unexpected INVALID_CHARACTER (Unix shell quoting issues?)
  at <top-level>, line 1:
```


### フィルタファイルの使い方

これらファイルを`jq`から呼び出すときは、次のオプションを指定します。

- `-f`オプション（`--from-file`）ーフィルタファイルを読み込みます。
- `-r`オプション（`--raw-output`）ー文字列を囲む二重引用符（`"`）を外します。

まとめると、次のようになります。

```
$ jq -rf tabular.jq tabular-sample.json
```

### タブ区切り

オブジェクトの値を配列化し、それをタブ記号（`\t`）で連結するというのが、最も簡単な手でしょう。

```
$ cat tabular-sample.json | jq -r '.[] | [.name, .region] | join("\t")'
```

JSON全体を囲む`[]`の要素でループ（上記`.[]`）し、それぞれについて`name`と`region`の値を要素とした配列を生成し（`[.name, .region]`）、それら要素を`join()`で連結するわけです。結果は次のようになります。

```
Penfolds Bin 389        South Austraria
Chateau Cantenac Brown  Margaux
Tenuta San Guido Le Difese      Bolgheri
```

おおむねできました。しかし、左側（`name`）の文字列の長さに幅があるので、タブでは2列目がうまく揃いません。


### スペースでパディング

こういうときは文字列をスペースでパディングし、あとから同じ長さで切り揃えます。強引ですが、`sprintf()`のないJavaScriptでよくやる手です。

ここでの文字列は最長で26文字なので、30文字のスペースを右側に加え（`+ " "*30`）て右パディングした上で、スライス（`[:30]`）で左から30文字を切り取ります。

```
$ cat tabular-sample.json | jq -r '.[] | [(.name + " "*30)[:30], .region] | join("")'
```

結果は次の通りです。

```
Penfolds Bin 389                South Austraria
Chateau Cantenac Brown          Margaux
Tenuta San Guido Le Difese      Bolgheri
```


### 最長文字列に合わせる

これだと、最初に最長文字を確認しなければならないので面倒です。そこで、事前に最長文字列を`length`と`max`から調べ、変数`$namelen`に収容します。

フィルタは次の通りです。

```
. |
(                                                   # 変数に最大長+5を代入
    [
        .[] |
        .name |                                     # それぞれの name の
        length                                      # 長さ
    ] |
    max + 5                                         # 最大値+5
) as $namelen |                                     # $namelen に代入
.[] |                                               # あとは同じ
[
    (.name + " " * $namelen)[:$namelen],
    .region
] |
join("")
```

最初の`.|`で、入力JSONテキストをそのまま次段（2～9行目）の最大長の計算にパイプします。`() as $namelen`は代入式なので、その先（10行目）の`.`に影響は与えません。

まず、入力全体を`.[]`でループし、逐次`.name | length`で文字列長を取得します。このループは`[]`（3行目と7行目）で括られているので、生成されるのは`name`の文字列長の配列です。これを`max`（8行目）に通すと、最大値が得られます。ここでは1列目と2列目の間に5つのスペースを加えるために、追加で`+5`しています。結果は`as $namelen`で変数に代入します。

以降は、直値の代わりに変数を用いている以外、これまでと変わりません。

実行例を示します。

```
$ cat tabular-sample.json | jq -rf tabular.jq
Penfolds Bin 389               South Austraria
Chateau Cantenac Brown         Margaux
Tenuta San Guido Le Difese     Bolgheri
```

### おわりに

ええ、実用性はゼロです。Unixユーザなら[`column`](https://linuxjm.osdn.jp/html/util-linux/man1/column.1.html)を使いますよね。

```
$ jq -r '.[] | [ .[] ] | join("|")' tabular-sample.json | column -t -s "|"
Penfolds Bin 389            South Austraria
Chateau Cantenac Brown      Margaux
Tenuta San Guido Le Difese  Bolgheri
```


### 参考

- [./jq](https://stedolan.github.io/jq/) - `jq`のオフィシャルサイトです（英文）。
- [man page of COLUMN](https://linuxjm.osdn.jp/html/util-linux/man1/column.1.html) - Unixコマンドの`column`のman pageです。
- [jqマニュアル（開発バージョン）](https://yujiorama.github.io/unofficial-translations/jq/l10n/jp/manual/) - Yuji Okazawa氏が（非公式に）訳しておられる`jq`マニュアルの日本語版です。和訳マニュアルは他にもあるので、検索してください。
- [The JavaScript Object Notation (JSON) Data Interchange Format（RFC 8259）](https://www.rfc-editor.org/rfc/rfc8259.txt) - JSONの仕様です（英文）。しばしば[RFC 7159](https://www.rfc-editor.org/rfc/rfc7159.txt)が参照されますが、そちらはobsoleteになりました。
- [JSON(JavaScript Object Notation)データ交換フォーマット](https://www.asahi-net.or.jp/~ax2s-kmtn/internet/rfc8259j.html) - 上記の非公式の和訳です。
- [jqハンドブックーNetOps/DevOps必携のJSONパーザ](http://www.cutt.co.jp/book/978-4-87783-491-3.html) - 表形式出力はこちらでも取り上げています。【[出版社](http://www.cutt.co.jp/book/978-4-87783-491-3.html) | [honto](https://honto.jp/netstore/pd-book_31037910.html) | [amazon.co.jp](https://www.amazon.co.jp/jq/dp/4877834915) | [ヨドバシカメラ](https://www.yodobashi.com/product/100000009003450000/) 】。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/20272c85-f084-557b-189f-aa7128e283ab.png)