【JavaScript】JSON.parse()とJSON.stringify()でのオブジェクトコピーはダメな理由
更新日:2024/02/27
JavaScriptでオブジェクトをコピーする方法として、JSON.parse()とJSON.stringify()を使用する方法が紹介されることが多いです。
しかし、この方法はあまりおススメしません。
その理由をお伝えします。
JSON.parse()とJSON.stringify()
まずは、使い方を確認します。
JSON.parse()とJSON.stringify()は、ディープコピーで使用されることが多いですね。
ディープコピーとは、入れ子になったオブジェクトを辿ってコピーすることを指します。
次のように使用します。
const obj1 = { a:1 , b: { c:2 , d:{e:3} }};
const obj2 = JSON.parse( JSON.stringify(obj1) );
obj2.b.d.e = "change!";
console.log( obj1 ); // 結果: { a: 1, b: { c: 2, d: { e: 3 } } }
console.log( obj2 ); // 結果: { a: 1, b: { c: 2, d: { e: 'change!' } } }
コピー先(obj2)の値を変更しても、コピー元(obj1)は影響を受けていません。
ディープコピーできていますね。
仕組みとJSON
仕組みとしては、JSON.stringify()がオブジェクトの構造を読み取って文字列します。
次に、その文字列を設計図としてJSON.parse()がオブジェクトを組み立てます。
JSONは、主にサーバーとブラウザ間のデータのやり取りに使用されます。
その他の情報は、やり取りされません。
その他の情報とは、オブジェクトが持っている機能などです。
例えば、関数オブジェクトが持っている関数を実行する機能や、Mapオブジェクトが持っているキーと値のペアを管理する機能、Dateオブジェクトが持っている日時データを管理する機能などです。
これらは代替的なデータ(文字列や単純なオブジェクト)に変換されたり、削除されます。
const mp = new Map([["key1","value1"],["key2","value2"]]);
const obj1 = { 1:undefined , 2:()=>{} , 3:mp , 4:new Date()
,5:{ set a(v){},get a(){return "123"}} };
const obj2 = JSON.parse( JSON.stringify(obj1) );
console.log( obj1 );
// 結果: {
// 1: undefined
// 2: ()=>{}
// 3: Map(2) { key1 → "value1", key2 → "value2" }
// 4: Tue Aug 01 2023 15:40:30 GMT+0900 (GMT+09:00)
// 5: { a: [Getter/Setter] }
// }
console.log( obj2 );
// 結果: {
// 3 { }
// 4: "2023-08-01T06:40:30.592Z"
// 5: { a: "123" }
// }
1:undefined値と 2:関数が消えました。
3:Mapオブジェクトは、空のオブジェクトに変換されました。
4:Dateオブジェクトは、JSON.stringify()実行時の日時を文字列にしたものに変換されました。
また 5:セッターとゲッターは、ゲッターで取得した値に置き換わります。
循環参照はNG
オブジェクトのプロパティ値がオブジェクトを参照していて、参照がエンドレスにループする循環参照になっていると、JSON.stringify()はエラーをスローします。
const obj1 = { a:1 , b: { c:2 , d:{e:3} }};
obj1.f = obj1;
const obj2 = JSON.parse( JSON.stringify(obj1) );
// Uncaught TypeError: Converting circular structure to JSON
ブラウザのDOM関連オブジェクトは循環参照していることが多いので、この方法は使用できませんね。
問題ないことを確信できるときのみ使う
JSON.parse()とJSON.stringify()を使用したコピーは、いくつか問題があることがわかりました。
そのため、この二つのメソッドを使用するときは、次の点を確認する必要があります。
- プロパティ値が循環参照していない
- プロパティ値がundefinedでない
- プロパティ値がオブジェクトの時、問題なく変換できる
3番目は、組み込みオブジェクト等を避けて、自作オブジェクトのみに限定するのが無難です。
structuredClone()を使用する
そもそもオブジェクトを文字列に変換するのは、効率が悪い気がしますね。
そもそもJSON.parse()とJSON.stringify()は、オブジェクトコピーを目的としたメソッドではありません。
この方法が使用され始めた頃は、他に手軽な方法がありませんでした。
現在はstructuredClone()という関数が実装されています。
こちらを使用しましょう。
更新日:2024/02/27
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。