stream-json で json データを stream のまま編集してみた

2023.05.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

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 ファイルを扱う際に有効かと思われます。

検証のために使ったコードはここにおいてあります。