JSON.parse(JSON.stringify(obj))をやめよう

TL:DR;

JavaScriptでDeepCopyしたいなら structuredClone を使おう。

本題

JS書いてると、なんだかんだDeepCopyする場面、ありますよね!

ぐぐると、よく出てくるのが JSON.parse(JSON.stringify(obj)) という方法です。

const obj = { a: 1, b: { c: 2 } };
const copy = JSON.parse(JSON.stringify(obj));

こういうの、書いたこと無いですか?ぼくはあります。

ひょんなことから、これのパフォーマンスが気になるタイミングがあったので、調べてみると、Node v17で structuredClone という関数が追加されていました。(ブラウザでも2021年ごろから使えるようです。)

const obj = { a: 1, b: { c: 2 } };
const copy = structuredClone(obj);

ということで、比較しました

実験

でかいオブジェクトを用意して、JSON.parse(JSON.stringify(obj))structuredClone(obj) のパフォーマンスを比較してみます。

こんなオブジェクトを用意しました。要素数Nは1000, 10000, 100000, 1000000の4パターンで、各要素は100文字の文字列です。

obj = {
  data: [ //要素数N
    "aaaaa....aaaaa", // 100文字の文字列
    "aaaaa....aaaaa",
    ...
    "aaaaa....aaaaa",
    "aaaaa....aaaaa" 
  ]
}

計測は WSL2 (Ubuntu 22.04LTS), で行いました。

結果

パフォーマンス比較

要素数NJSON.parse(JSON.stringify)structuredClone
10000.948 ms1.405 ms
1000011.439 ms2.093 ms
10000076.834 ms27.527 ms
10000001264 ms428.378 ms

グラフにすると、以下のようになります。

パフォーマンス比較グラフ

計測した結果、JSON.parse(JSON.stringify(obj)) のほうが遅いのがわかりました。

もうブラウザにもNodeにも structuredClone があるので、これを使うのが良いみたいです。

参考


実験コード

// テスト用オブジェクトを生成する関数
function createObject(numItems, itemLength) {
  const obj = {
    data: [],
  };
  const baseString = 'a'.repeat(itemLength);
  for (let i = 0; i < numItems; i++) {
    obj.data.push(baseString + i);
  }
  return obj;
}

// パフォーマンスを計測する関数
function measurePerformance(method, obj, label) {
  console.time(label);
  const copiedObj = method(obj);
  console.timeEnd(label);
}

// メインの比較処理
function runComparison() {
  const itemLength = 100; // 配列内の各文字列の長さ
  const N_values = [1000, 10000, 100000, 1000000]; // 配列の要素数

  console.log(`文字列長: ${itemLength}`);
  console.log('-----------------------------------');

  for (const N of N_values) {
    console.log(`配列の要素数 (N): ${N}`);
    const testObject = createObject(N, itemLength);

    // JSON.parse(JSON.stringify()) のパフォーマンス計測
    measurePerformance(
      (obj) => JSON.parse(JSON.stringify(obj)),
      testObject,
      'JSON.parse(JSON.stringify)'
    );

    // structuredClone のパフォーマンス計測
    if (typeof structuredClone === 'function') {
      measurePerformance(
        (obj) => structuredClone(obj),
        testObject,
        'structuredClone'
      );
    } else {
      console.log('structuredClone is not available in this environment.');
    }
    console.log('-----------------------------------');
  }
}

runComparison();