JSON - を node の Stream で整形する
motivation
- JSONは本来JavaScriptから生じたものだからどうせならJavaScriptでやりたいし
まあ、 JSON ですしね。
- そのJavaScriptに今や標準搭載のJSON.stringify()は実はpretty printできるし
確かに stringify でできますね。(昔こちらにも書きました。)
- どうせなら標準入力だけではなくURIやファイル名で直アクセスしたいし
同意です。
しかし、実装を見てみると、、
(以下、主要な部分の抜粋)
// stdout stdin.on("data", voorhees);
// http http.get(req, function (res) { res.on('data', function (data) { chunks.push(data) }).on('error', function (e) { console.log(e.message); }).on('end', function () { voorhees(chunks.join('')) }); });
// fs fs.readFile(argv[2], function (err, data) { if (err) throw err; voorhees(data); });
実は、こうした「データソースから受け取ったデータを加工して、次に渡す」といったものこそ、 Stream に向いています。
正確には、今回の用途は「データをまとめて扱っている」ので、 Stream にする必要はかならずしもないです。
しかし、 Stream にしておくと以下のようなメリットがあるかと思います。
- すでに Stream として抽象化されているものとの接続製があがる。(substack や dominic のライブラリ集が使える!)
- うまくやれば、 JSON のストリーム(次々と途切れなく流れてくる JSON の配列とか) を、途中途中で Stringify しながら画面表示したりできる。
- voorhees は CLI を提供するフロントの層と JSON を整形するロジックの層が1つになっています(それ自体をどうこう言う気はないです) が、こうしたレイヤの分離も Stream を使うとうまくいったりします。
そしてなにより、先の 3 つのリソース(stdin/out, file, http) は全て、標準ですでに Stream として抽象化されています。
乗るしか無い、このエコシステムに!
Transform Stream
今回の用途だと、 Transform Stream にすることになります。特徴だけ説明します。
今回は、チャンクごとに処理せず、まとめて最後に処理するので、 _transform() はデータを溜めるだけで、 _flush() で全部 stringify() 処理します。
JSPP.prototype._transform = function(chunk, output, cb) { chunk = chunk.toString(); if (chunk) { this.data += chunk; } return cb(null); }; JSPP.prototype._flush = function(output, cb) { if (this.data) { try { output(this._pp(this.data)); } catch(e) { return cb(e); } } cb(null); };
使う場合は、 pipe するだけです。
// 標準入出力の場合 process.stdin.pipe(jspp).pipe(process.stdout); // http の場合 http.get(url, function (res) { res.pipe(jspp).pipe(process.stdout); }); // ファイルの場合 fs.createReadStream(file).pipe(jspp).pipe(process.stdout);
こうして、標準モジュールや、他の Stream を実装したライブラリと、好き放題繋ぐ事ができます。
jspp-stream
ということで、以前作った ltsv-stream をちょちょっといじって、 jspp(json pritty print) stream ということで実装してみました。
https://github.com/Jxck/jspp-stream
Stream 実装(jspp-stream.js)だけでなく、これを使った voorhees 互換の jspp コマンドも同梱してあるので、 npm -g で入れれば使えます。
(こうして、ロジックの層とフロントの層が簡単に分けられるのも、 Stream を使うメリットの1つです)
$ npm install -g jspp-stream $ cat test.json | jspp $ jspp /path/to/file.json $ jspp http://some.json.com
テストもエラー処理も適当ですが、まあこんな感じでできますよということで。