JSON.parseとJSON.stringifyでらくらく配列のディープコピー

現在午前4時なのですが、仙台ではセミが鳴いています。皆さん今日も元気にpushしておりますか。

pushといえば最近ではgitですが、JavaScriptの配列のコピーなどでも使いますよね。
concatでもいいんですが、あいつは多次元配列でも容赦無いのでここでは華麗にスルーします。
ゲームを作っていたのですが、その時に配列のコピーで悩んだことがあったのでメモしておきます。

参照でハマる

下記のようなオブジェクトがあったとした時に、

shop = {};
shop.chara ={
'elmo':{
'name':'えるも',
'item':'あいす',
'itemList':{
'ice':{
'name':'アイス',
'list':[
//商品名,商品説明,svg色配列['class','プロパティ','値']
['バニラアイス','バニラあじのアイス', [['outline','stroke','#ccc'],['top','fill','#ccc'],['bottom','fill','#000']]],
['ストロベリーアイス','イチゴあじのアイス',[['outline','stroke','red'],['top','fill','#ccc'],['bottom','fill','#000']]],
['チョコアイス','チョコあじのアイス', [['outline','stroke','#000'],['top','fill','#ccc'],['bottom','fill','#000']]],
['メロンアイス','メロンあじのアイス', [['outline','stroke','green'],['top','fill','#ccc'],['bottom','fill','#000']]],
['みかんアイス','みかんあじのアイス', [['outline','stroke','orange'],['top','fill','#ccc'],['bottom','fill','#000']]]
]
},
'chipice':{
'name':'アイス',
'list':[
['チョコチップミントアイス','ミントアイスのなかにチョコチップがはいったアイス',[['outline','stroke','#ccc'],['inner','fill','green']]],
['スイカアイス','スイカあじのアイスのなかにチョコがはいったアイス', [['outline','stroke','#ccc'],['inner','fill','green']]],
['クッキーバニラ','バニラアイスの中にクッキーがはいったアイス', [['outline','stroke','#ccc'],['inner','fill','brown']]]
]
}
}
}
};
view raw sample.js hosted with ❤ by GitHub

オブジェクト内の配列をまるごとコピーして、更にキーを配列の最初に取り付けたいというわがままコードを作成していたのですが、まんまと参照という罠に引掛かって2度めのアクション時に壮大にバグりました。

アイスの種類の左側にiceやchipiceというカテゴリーをつけたかったのですが、参照だったため2度目以降はice,iceとかダブるという素晴らしいコードになってしまいました。 一番簡単なのはオブジェクトに書き足すということなのですが、負けたくなかったため頑張りました。

ケース1 pushとかしまくる

配列を走査して、配列かどうかを確認しpushとかしまくる。配列の値が長いかつ面白く無いので却下

ケース2 JSON.parseとJSON.stringifyで完結

JSON.parse(JSON.stringify(配列やらオブジェクトやら))

もともとJSONだったのを埋め込みたいがためにオブジェクトにしたためてんてこまいだったけど、JSONでパースしてしまえばオブジェクトでも配列でもなんでもかんでもディープコピーできるので楽。

コード

shop = {};
shop.chara ={
'elmo':{
'name':'えるも',
'item':'あいす',
'itemList':{
'ice':{
'name':'アイス',
'list':[
//商品名,商品説明,svg色配列['class','プロパティ','値']
['バニラアイス','バニラあじのアイス', [['outline','stroke','#ccc'],['top','fill','#ccc'],['bottom','fill','#000']]],
['ストロベリーアイス','イチゴあじのアイス',[['outline','stroke','red'],['top','fill','#ccc'],['bottom','fill','#000']]],
['チョコアイス','チョコあじのアイス', [['outline','stroke','#000'],['top','fill','#ccc'],['bottom','fill','#000']]],
['メロンアイス','メロンあじのアイス', [['outline','stroke','green'],['top','fill','#ccc'],['bottom','fill','#000']]],
['みかんアイス','みかんあじのアイス', [['outline','stroke','orange'],['top','fill','#ccc'],['bottom','fill','#000']]]
]
},
'chipice':{
'name':'アイス',
'list':[
['チョコチップミントアイス','ミントアイスのなかにチョコチップがはいったアイス',[['outline','stroke','#ccc'],['inner','fill','green']]],
['スイカアイス','スイカあじのアイスのなかにチョコがはいったアイス', [['outline','stroke','#ccc'],['inner','fill','green']]],
['クッキーバニラ','バニラアイスの中にクッキーがはいったアイス', [['outline','stroke','#ccc'],['inner','fill','brown']]]
]
}
}
}
};
var charaArr =[];
function charaArrSet(prop,obj){
// すべてのアイテムを走査し、配列に変換する
var arr = [];
// キーを配列の最初にに加えて作成したい
for (var i = 0; obj.list.length>i; i++) {
arr[i]=JSON.parse(JSON.stringify(obj.list[i]));
arr[i].unshift(prop);
}
// charaArrにセット
for (var j = 0; arr.length>j; j++) {
charaArr.push(arr[j]);
}
}
var character = shop.chara.elmo;
for (var prop in character.itemList) {
if (character.itemList.hasOwnProperty(prop)) {
var obj = character.itemList[prop];
charaArrSet(prop,obj);
}
}
console.log(charaArr);
view raw deepcopy.js hosted with ❤ by GitHub

まとめ

Firefox deepcopy firefox

Chrome deepcopy chrome

Chromeでは、なぜかJSON.parseしたやつだと、配列をいじるかなにかしないとインスペクタでバグってるような感じになる。しかしながらちゃんと配列として格納されているのでインスペクタがおかしいんだと思います。

IE7以下の呪われしブラウザ以外は動くので配列やオブジェクトをコピーしたい場合には楽だと思います。 全然関係ないですけどライブラリを用いないでゲームを作ると自分のスキルの物足りなさを実感できるのでオススメ。