Node.js の stream を使うと、大きなデータを部分的にメモリ展開して確認することができます。
stream-json は json 形式の文字列データの stream を編集することができるライブラリです。 これをつかってどんなことができるのか、基本的な使い方を紹介します。
使い方
以下のような json ファイルがあると仮定します。
[
{ "timestamp": 1682640000000, "temperature": 22 },
{ "timestamp": 1682643600000, "temperature": 27 },
{ "timestamp": 1682647200000, "temperature": 31 },
{ "timestamp": 1682650800000, "temperature": 33 },
{ "timestamp": 1682654400000, "temperature": 29 },
{ "timestamp": 1682658000000, "temperature": 27 },
{ "timestamp": 1682661600000, "temperature": 30 },
{ "timestamp": 1682665200000, "temperature": 35 },
{ "timestamp": 1682668800000, "temperature": 31 },
{ "timestamp": 1682672400000, "temperature": 28 }
]
これをfs.createReadStream()
で読み取り、json を編集していみます。
pick()
pick()
を使うと、特定の要素を抜き出して新たな json を作成することができます。
以下の例ではtimestamp
だけを取り出したあらたな json を作成しています。
import { createReadStream } from "node:fs";
import { parser } from "stream-json";
import { pick } from "stream-json/filters/Pick";
import { stringer } from "stream-json/Stringer";
const stream = createReadStream("./simple-array-data.json")
.pipe(parser())
.pipe(pick({ filter: /\d+\.timestamp/ }))
.pipe(stringer({ makeArray: true }));
// [
// 1682640000000,
// 1682643600000,
// 1682647200000,
// 1682650800000,
// 1682654400000,
// 1682658000000,
// 1682661600000,
// 1682665200000,
// 1682668800000,
// 1682672400000
// ]
filter()
filter()
を使うことで、json の階層を変えないまま特定の要素だけを残した json を新たに作成することができます。
以下の例では 5 の倍数の要素だけに間引きした json を作成しています。
import { createReadStream } from "node:fs";
import { parser } from "stream-json";
import { filter } from "stream-json/filters/Filter";
import { stringer } from "stream-json/Stringer";
const stream = createReadStream("./simple-array-data.json")
.pipe(parser())
.pipe(
filter({
filter: (stack) => typeof stack[0] === "number" && stack[0] % 5 === 0,
})
)
.pipe(stringer());
// [
// {"timestamp":1682640000000,"temperature":22},
// {"timestamp":1682658000000,"temperature":27}
// ]
stream-json
にはpick()
とfilter()
の他にもignore()
とreplace()
などの機能が用意されています。
streamValues()
Values 系の機能を使うことで json 形式の文字列データをオブジェクトとして取り出すことができます。
Values 系の機能は stream-json
の作者が手掛ける別のライブラリ stream-chain
と一緒に使います。
以下の例では streamValues()
を使って配列の要素をオブジェクトとして取り出し、特定の timestamp を持つ要素のみを残した json を作成しています。
import { createReadStream } from "node:fs";
import { chain } from "stream-chain";
import { parser } from "stream-json";
import { pick } from "stream-json/filters/Pick";
import { streamValues } from "stream-json/streamers/StreamValues";
import { disassembler } from "stream-json/Disassembler";
import { stringer } from "stream-json/Stringer";
const stream = chain([
createReadStream("./simple-array-data.json"),
parser(),
pick({ filter: /\d+/ }),
streamValues(),
({ value }) => {
if (1682654400000 <= value.timestamp && value.timestamp < 1682661600000) {
return [value];
}
},
disassembler(),
stringer({ makeArray: true }),
]);
// [
// { timestamp: 1682654400000, temperature: 29 },
// { timestamp: 1682658000000, temperature: 27 },
// ];
まとめ
今回調べた stream-json を用いることで、json ファイルをメモリ展開せずに、stream のまま書き換えが可能です。 lambda などの memory が限られたランタイムで、大きな json ファイルを扱う際に有効かと思われます。
検証のために使ったコードはここにおいてあります。