データ構造が決まるとアルゴリズムも自ずから定まる、みたいな話もあるように、ソフトウェアエンジニアリングの文脈では「どうデータを持つか」が重要だ。これはちょっとしたシェルスクリプトでも同様。
自分はUNIX哲学が好きで、なんかちょっとしたツールを作るときは基本的に1行1データのテキスト形式を喋るように作成する。そのほうが適用できるツールが多くなるからだ。shellscriptを使うときはwhile read
文を使えば1行1コマンドを実行する処理をすぐ書けるし、sed
を使えば基本的な加工ができる(互換性の問題があるから基本的にgsedを使うけど)。開発や運用にLinuxを使っている以上、こういう形式を採用しておくと取り回しやすくなる。
その一方、1行1データのテキスト形式はそれ自体では構造化されていないという問題がある。ちょっとでも複雑な形式で書こうとすると、CSVやTSVにしようとか、:
でセパレートしたレコードにしようとか、Labeled Tab-separated Values (LTSV)にしようとか考えるのだけれど、構造化したいという要件が生じている時点でもう面倒になるのは目に見えている。それに、仮にデータ構造が壊れていても、UNIX系ツールでは壊れたまま進んでしまいがち。
できれば、構造化という話題をうまく外部ツールに逸らしたいのだけれど、こういうときにJSON Linesが便利だ。
JSON Lines
JSON Linesとは、1行1JSONオブジェクトという形式で定義されたファイルで、拡張子は.jsonl
を使うのが慣例になっている。
{"id": 1, "text": "foo"} {"id": 2, "text": "bar"}
こういう感じのファイルだ。
JSON Linesになっていると、まずシェルスクリプト等で1行読みつつ、jq
を組合わせてデータを取り出す、といったことが簡単にできる:
while read -r line; do body=$(echo "$line" | jq -cr '.body // empty') done < data.jsonl
また、JSON Schemaと組合わせることで、事前にデータが正しい形式で記録されているかどうかを検証できる(ここでは、validatorとしてajv
/ajv-cli
を使っている):
while read -r line; do # nodejs does not support process substitution !!!! tmpjson=$(mktemp --suffix=.json) echo -n "$line" > "$tmpjson" set +e ajv validate -s "$JSONSCHEMA_FILE" -d "$tmpjson" > /dev/null exitcode=$? rm "$tmpjson" done < data.jsonl
事前にデータを全部validateしてから実際の処理を開始するようにしておくと、中途半端な位置で停まったりしないので助かる。
nodejsがプロセス置換の仕組みに対応していないので、いったん一時ファイルに書き出しているが、本当ならajv validate -s "$JSONSCHEMA_FILE" -d <(echo $line) > /dev/null
と書けるはずだ。いや本当はajv-cli
がSTDINからの入力に対応すればいいだけなんだけど。ノットUNIXフィロソフィーじゃんよ。
JSON Linesは、jq
とvalidatorがあれば大抵のことができるので、せいぜい2つくらいの依存があればよいという利点がある(ajv
はnpm経由で入れることになるので面倒なのだが)。jq
はまあそれなりにどのディストリビューションでも手に入るようになってきている。
加えて、JSONは構造化されているので、カラムの追加に強いという利点がある。ちょっとカラムを増やしたせいでコードが全体的に壊れてしまうという経験を誰もがそれなりにしていると思うのだけれど、JSONだとそういう心配はあまりない。また、コメントは「コメントを表現するJSON」を押し込むことで表現できるのが面白い。
いかがでしたか?
DB使うまでもないし、主導権はシェルスクリプトの側にあってrubyとかからXMLやJSONを読み取るほどのデータではない、しかし素のテキストだと不安、くらいのギャップをJSON Linesは埋めてくれるように思う。覚えておくと便利なので、ぜひ使ってみてください。