Help us understand the problem. What is going on with this article?

jqが使えなくても大丈夫なJavaScriptシェル芸

jqをインストールできなかったり、jqで複雑な加工をさせたいが構文が難しくて覚えられなかったりと、
なんらかの事情でjqを使わずjavascript(Node.js)でJSONを処理したいことが稀にあるかもしれません。

Node.jsは標準入力に弱い?そんな事はありません。ワンライナーで処理できます!

JSONファイルが小さいとき

下記のようなJSONを想定します。1

sample.json
{
  "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ファイルがあるとします。

sample.big.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の制限にかからないワンライナーにします。

support-large-json
$ 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だけを抽出してみます。

support-large-json
$ 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」のように省略されてしまいます。

出力がきれいになるように修正します。

beautifully
$ 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だけを抽出してみます。

beautifully
$ 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"
]
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away