深いコピー(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);
JavaScript2) 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
JavaScript3) 失われる型を安全に扱う(事前変換)
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練習問題(手を動かして覚える)
- 深いコピーしてネストが独立することを確認
const o = { a: { b: 1 } };
const c = JSON.parse(JSON.stringify(o));
c.a.b = 9;
console.log(o.a.b); // 1
JavaScript- 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- 循環参照で失敗することを確認
const o = {}; o.self = o;
try {
JSON.stringify(o);
} catch (e) {
console.log("循環参照で失敗:", e.message);
}
JavaScript直感的な指針
- プレーンなデータだけなら: JSON法で手早く深いコピー。
- 型を保ちたい/循環があるなら: structuredCloneなど別手段。
- Dateや特殊値は: 復元時に明示的に型変換。
- 巨大データでは: コストに注意、必要最小限で使う。
