jqをインストールできなかったり、jqで複雑な加工をさせたいが構文が難しくて覚えられなかったりと、
なんらかの事情でjqを使わずjavascript(Node.js)でJSONを処理したいことが稀にあるかもしれません。
Node.jsは標準入力に弱い?そんな事はありません。ワンライナーで処理できます!
JSONファイルが小さいとき
下記のようなJSONを想定します。1
{
"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}
}
ワンライナーで処理します。
$ cat sample.json | xargs -0 -i node -pe '({})'
{ menu:
{ id: 'file',
value: 'File',
popup:
{ menuitem:
[ { value: 'New', onclick: 'CreateNewDoc()' },
{ value: 'Open', onclick: 'OpenDoc()' },
{ value: 'Close', onclick: 'CloseDoc()' } ] } } }
menuitemのvalueだけを抽出してみます。
## 少しフィルタしてみる
$ cat sample.json | xargs -0 -i node -pe '({}).menu.popup.menuitem.map(_=>_.value)'
[ 'New', 'Open', 'Close' ]
簡単ですね。
大きなJSONを処理できない問題
しかし、この方法だと実は限界があります。
下記のような単純で少しサイズの大きなJSONファイルがあるとします。
{ docs:
[ { "_id": "14cd7192b2d699a606bde01c3123c750" },
{ "_id": "14cd7192b2d699a606bde01c3123d685" },
{ "_id": "14cd7192b2d699a606bde01c3123ddd4" },
{ "_id": "14cd7192b2d699a606bde01c3123f441" },
{ "_id": "14cd7192b2d699a606bde01c3123f6df" },
{ "_id": "14cd7192b2d699a606bde01c31240655" },
{ "_id": "14cd7192b2d699a606bde01c31241075" },
{ "_id": "14cd7192b2d699a606bde01c31241d31" },
{ "_id": "14cd7192b2d699a606bde01c31242a44" },
// 省略
]
}
確認すると69KBあります。
## ファイルサイズ(byte)
$ cat sample.big.json | wc -c
69656
先程の方法でJSONを処理しようとしてみます。
$ cat sample.big.json | xargs -0 -i node -pe '({})'
xargs: argument line too long
引数(与えられたJSONの文字列)が大きすぎると怒られます。
これはシェルが展開した文字列が OSの引数の制限 (正確には execve(2) の制限) を越えると動かなくなるためです。2
OSの引数に指定可能な文字数の最大値は下記で確認できます。
$ getconf ARG_MAX
32000
Git Bash上で実行すると32KBくらいまでみたいです。
ちなみにGCPのCloud Shellだと2MB(2097152byte)でしたので、Git bash相当小さいようですね。。。
大きなJSONに対応する
OSの制限にかからないワンライナーにします。
$ cat <(sed '1s/^/_=/' sample.big.json) <(echo _) | node -p -
{ docs:
[ { _id: '14cd7192b2d699a606bde01c3123c750' },
{ _id: '14cd7192b2d699a606bde01c3123d685' },
{ _id: '14cd7192b2d699a606bde01c3123ddd4' },
{ _id: '14cd7192b2d699a606bde01c3123f441' },
{ _id: '14cd7192b2d699a606bde01c3123f6df' },
... 791 more items ]
}
docsの_idだけを抽出してみます。
$ cat <(sed '1s/^/_=/' sample.big.json) <(echo '_.docs.slice(0,3).map(v=>v._id)') | node -p -
[ '14cd7192b2d699a606bde01c3123c750',
'14cd7192b2d699a606bde01c3123d685',
'14cd7192b2d699a606bde01c3123ddd4' ]
jqのようにきれいに整形
今まで紹介した方法でシンプルに処理できますが出力されるJSONは、ダブルコーテーションが外れ、インデントが微妙になり、大きなリストは「... xxx more items」のように省略されてしまいます。
出力がきれいになるように修正します。
$ cat <(sed '1s/^/_=/' sample.big.json) <(echo 'JSON.stringify(_,null,"\t")') | node -p -
{
"docs": [
{
"_id": "14cd7192b2d699a606bde01c3123c750"
},
{
"_id": "14cd7192b2d699a606bde01c3123d685"
},
{
"_id": "14cd7192b2d699a606bde01c3123ddd4"
},
{
"_id": "14cd7192b2d699a606bde01c3123f441"
},
{
"_id": "14cd7192b2d699a606bde01c3123f6df"
},
{
"_id": "14cd7192b2d699a606bde01c31240655"
},
//省略しますが全部出力されます
]
}
きれいに出力されてますね。
docsの_idだけを抽出してみます。
$ cat <(sed '1s/^/_=/' sample.big.json) <(echo 'JSON.stringify(_.docs.slice(0,3).map(v=>v._id),null,"\t")') | node -p -
[
"14cd7192b2d699a606bde01c3123c750",
"14cd7192b2d699a606bde01c3123d685",
"14cd7192b2d699a606bde01c3123ddd4"
]
まとめ
~~jq を使ったほうが早そうな気……~~これでNode.jsさえあればjq使わなくても大丈夫ですし、
複雑な加工でも学習コスト0で気軽にJSONを加工できるようになりますね!
他に良い方法があればコメントお願いします。
備考
jq is awesome !
$ cat sample.big.json | jq [.docs[0:3][]._id]
[
"14cd7192b2d699a606bde01c3123c750",
"14cd7192b2d699a606bde01c3123d685",
"14cd7192b2d699a606bde01c3123ddd4"
]