JavaScript 逆引き集 | 深いコピー(JSON)(注意:関数/undef は除外)

JavaScript JavaScript
スポンサーリンク

深いコピー(JSON)— JSON.parse(JSON.stringify(obj)) の基本と実践

「オブジェクトをまるっと別インスタンスにしたい」場面でよく使われる簡易テクニックが JSON.parse(JSON.stringify(obj))。JSONに一度落としてから復元することで、ネスト(入れ子)も含めた“深いコピー”に近い形を手早く作れます。ただし保存できる型に制約があり、使いどころを見極めるのがコツです。


仕組みと基本の使い方

const original = { a: 1, nested: { b: 2 } };
const clone = JSON.parse(JSON.stringify(original));

clone.nested.b = 99;
console.log(original.nested.b); // 2(元は変わらない)
JavaScript
  • 流れ: stringifyで「JSON文字列化」→ parseで「オブジェクト化」。
  • 効果: ネストの参照も切れるため、浅いコピー {...obj} と違い、内側を変えても元に影響しません。

使える型/失われるもの

  • 保持される代表例: 数値、文字列、真偽値、null、配列、プレーンオブジェクト
  • 失われる・変換されるもの:
    • 関数 / undefined / Symbol: そもそもJSONにできないため落ちる(欠落)
    • Date: 文字列(ISO)として保存・復元される(Dateインスタンスではなくなる)
    • NaN / Infinity: null になる
    • BigInt: 例外(エラー)
    • Map / Set / RegExp / Error / クラスインスタンス: 構造が失われる(プレーン化・欠落・文字列化など)

よくある落とし穴と対策

  • 循環参照で失敗する: obj.self = obj のような自己参照は stringifyでエラー。
    → 循環を事前に除去、または別手法(後述の structuredClone)。
  • Dateがただの文字列になる:
    → 復元時に reviver のような処理で new Date(value) に戻す。
  • 型が落ちる(Map / Set / クラスなど):
    → ルール化して独自シリアライズ/デシリアライズ、または structuredClone を使う。
  • 巨大データで遅い: JSON化は文字列化・パースが重い。
    → 頻繁な複製は避ける、またはより高速な手段を検討。

すぐ使えるテンプレート集

1) 基本の深いコピー

const cloneDeepByJSON = obj => JSON.parse(JSON.stringify(obj));

const src = { a: 1, nested: { b: 2 } };
const dst = cloneDeepByJSON(src);
JavaScript

2) Dateを“元の型”に戻す(復元処理)

const src = { when: new Date("2025-12-05T09:00:00+09:00") };
const json = JSON.stringify(src); // {"when":"2025-12-05T00:00:00.000Z"}

const dst = JSON.parse(json, (key, value) => {
  // ISO風文字列ならDateに戻す(簡易判定例)
  return (key === "when" && typeof value === "string" && /\d{4}-\d{2}-\d{2}T/.test(value))
    ? new Date(value)
    : value;
});

console.log(dst.when instanceof Date); // true
JavaScript

3) 失われる型を安全に扱う(事前変換)

const src = {
  id: 1n,               // BigInt(NG)
  token: Symbol("x"),   // Symbol(NG)
  calc: () => 123,      // 関数(NG)
};

// JSON化前に方針を決めて文字列へ
const safe = {
  id: String(src.id),
  token: String(src.token.description ?? "token"), // "x"
  // 関数は落とす or メタ情報にする
};

const copy = JSON.parse(JSON.stringify(safe));
JavaScript

実務での使い分け指針

  • “純粋データ”の複製ならOK: 設定・APIレスポンス・キャッシュなど、プレーンな値だけの構造に最適。
  • “型を維持したい”ならNG: Date/Map/Set/クラスなどを含むなら不適。
    → 代替として以下を検討。
    • structuredClone(obj): 近年のブラウザ・Nodeで利用可。多くの型に対応し、循環参照もOK。
    • ライブラリ: 変換ルールを持ちたい、レガシー環境で幅広い型を扱いたい場合。

代替手段(参考)

// 推奨:型を保った深いコピー(対応型が広く循環もOK)
const deep = structuredClone(src);

// レガシー向けにはライブラリや自作のシリアライズルールを検討
JavaScript

練習問題(手を動かして覚える)

  1. 深いコピーしてネストが独立することを確認
const o = { a: { b: 1 } };
const c = JSON.parse(JSON.stringify(o));
c.a.b = 9;
console.log(o.a.b); // 1
JavaScript
  1. Dateが文字列化されることを確認し、復元
const o = { when: new Date() };
const s = JSON.stringify(o);
const c = JSON.parse(s, (k,v) => k === "when" ? new Date(v) : v);
console.log(c.when instanceof Date); // true
JavaScript
  1. 循環参照で失敗することを確認
const o = {}; o.self = o;
try {
  JSON.stringify(o);
} catch (e) {
  console.log("循環参照で失敗:", e.message);
}
JavaScript

直感的な指針

  • プレーンなデータだけなら: JSON法で手早く深いコピー。
  • 型を保ちたい/循環があるなら: structuredCloneなど別手段。
  • Dateや特殊値は: 復元時に明示的に型変換。
  • 巨大データでは: コストに注意、必要最小限で使う。
タイトルとURLをコピーしました