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']]] | |
] | |
} | |
} | |
} | |
}; |
オブジェクト内の配列をまるごとコピーして、更にキーを配列の最初に取り付けたいというわがままコードを作成していたのですが、まんまと参照という罠に引掛かって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); |
まとめ
Firefox
Chrome
Chromeでは、なぜかJSON.parseしたやつだと、配列をいじるかなにかしないとインスペクタでバグってるような感じになる。しかしながらちゃんと配列として格納されているのでインスペクタがおかしいんだと思います。
IE7以下の呪われしブラウザ以外は動くので配列やオブジェクトをコピーしたい場合には楽だと思います。 全然関係ないですけどライブラリを用いないでゲームを作ると自分のスキルの物足りなさを実感できるのでオススメ。