JavaScriptでネストされたオブジェクトをディープコピーするならLodashの`cloneDeep`が確実

はじめに(結論)

JavaScriptやTypeScriptの開発において、「オブジェクトのコピー」は頻繁に発生する処理です。しかし、ネスト(階層)が深い複雑なオブジェクトをスプレッド構文(`…`)でコピーしようとすると、深い階層が参照渡しのままになってしまい、意図しないバグを生むことがあります。

結論から言うと、**階層の深いJSONデータなどを安全に複製したい場合は、Lodashの `cloneDeep` を使うのが最も確実で安全なアプローチ**です。本記事では、実務での複雑なデータ構造の複製を例に、具体的な使い方を解説します。

浅いコピー(シャローコピー)の罠

通常、JavaScriptでオブジェクトをコピーする際によく使われるのがスプレッド構文です。

const obj2 = { ...obj1 };

しかし、この方法は1階層目しか値コピーされず、2階層目以降は元のオブジェクトと同じメモリ参照を持ってしまいます。これを知らずに深い階層の値を書き換えると、元のオブジェクト(状態)まで破壊されてしまいます。

実装の実例:複雑なフォームデータのディープコピー

実際の開発で、以下のような非常に階層が深いユーザープロフィール登録フォームのデータ構造を扱うケースを想定してみましょう。

userData
└─ profile
    └─ addressInfo
        └─ shippingAddress
            └─ details
                ├─ 姓 (lastName)
                └─ 名 (firstName)

このフォームデータ全体を複製し、不要なフラグ(例えばシステム側で付与された `_isAutoGenerated` という一時的なフラグ)だけを全階層から取り除きたいという要件です。元の状態(State)を直接書き換えるのはNG(イミュータビリティの原則)であるため、完全に独立したコピーを作る必要があります。

Lodash `cloneDeep` を使った解決策

ここで活躍するのが Lodash の `cloneDeep` です。

import { cloneDeep } from 'lodash';

// ... (クラスや関数の内部)

removeTempFlags(userData) {
    // 1. cloneDeepで完全に独立したディープコピーを作成
    const clonedData = cloneDeep(userData);

    // 2. 複製したオブジェクトに対して再帰的に処理を行い、フラグを削除
    this.walkObject(clonedData, (node) => {
        delete (node)._isAutoGenerated;
    });
    
    // 3. 元のデータは無傷のまま、新しいデータを返す
    return clonedData;
}

この手法の素晴らしい点は、**元の `userData` オブジェクトには一切影響を与えずに、安全にデータの加工ができる**点です。

最近の代替手段: `structuredClone` について

最近のモダンブラウザやNode.js(v17以降)では、標準機能として `structuredClone()` というディープコピーAPIが追加されています。

const clonedData = structuredClone(userData);

標準APIであるためライブラリ不要というメリットがありますが、DOMノードや関数が含まれているとエラーになるなどの制約もあります。

既存のプロジェクトで既にLodashが導入されている場合や、複雑なクラスインスタンスなどを確実に処理したい場合は、引き続き `cloneDeep` が実績のある安定の選択肢となります。

まとめ

階層の深いオブジェクトを扱う際、中途半端なコピーは深刻なバグ(Stateの意図せぬ汚染)を引き起こします。複雑なJSONデータの複製や再帰的なデータ処理を行う際は、迷わず `cloneDeep` (または `structuredClone`)を使って、安全なディープコピーを行いましょう。