概要
JavaScriptで以下のようなオブジェクトをマージする方法について書きます。
本題は深いマージ(deep merge,deep copy)です
※functionが存在しないプロパティのみのオブジェクトを想定
const a = { familyName: '織田',
firstName: '吉法師',
address: '尾張',
sex: '男',
details:
{ character:
{ favoriteTactics: '奇襲',
favoriteWord: '天下布武',
favoritePlace: '京' },
ownedCastle: [ '清洲城' ] } };
const b = { familyName: '織田',
firstName: '信長',
details:
{ character: { favoriteTactics: '鉄砲活用' },
ownedCastle: [ '岐阜城', '安土城' ] } };
Object.assignで浅いマージ(shallow merge,shallow copy)
Object.assignをつかうと以下のようになる。
const a = {
familyName: '織田',
firstName: '吉法師',
address: '尾張',
sex: '男',
details: {
character: {favoriteTactics: '奇襲', favoriteWord: '天下布武', favoritePlace: '京'},
ownedCastle: ['清洲城']
}
};
const b = {
familyName: '織田',
firstName: '信長',
details: {
character: {favoriteTactics: '鉄砲活用'},
ownedCastle: ['岐阜城', '安土城']
}
};
const result1 = Object.assign(a, b);
console.log('result1=');
console.log(result1);
ちなみに、スプレッド構文をつかうと、Object.assignよりも短い記述で書ける
const result1= { ...a, ...b };
result1=
{ familyName: '織田',
firstName: '信長',
address: '尾張',
sex: '男',
details:
{ character: { favoriteTactics: '鉄砲活用' },
ownedCastle: [ '岐阜城', '安土城' ] } }
Object.assignにあるとおり、
Object.assign() はプロパティの値をコピーするため、深い複製を行うには別な方法を使用する必要があります。元の値がオブジェクトへの参照である場合、参照の値のみをコピーするからです。
変数aに存在するプロパティは、変数bの同プロパティのものでコピー(上書き)される。
だが、変数aにあるa.detailsにb.detailsの参照でコピーされてしまうため、当初のa.detailsの情報が失われる。
たとえば、a.details.character.favoriteWord(='天下布武')の情報は失われる。
そこで、プロパティの値がオブジェクトへの参照である場合にも、
そのオブジェクト以下のプロパティまで含めてコピーするために深いマージについて考える
深いマージ(deep merge,deep copy)
以下のような関数mergeDeeplyをつくって、オブジェクト以下のプロパティを再帰的にマージする深いマージ(deep merge,deep copy)をできるようにする。
function mergeDeeply(target, source, opts) {
const isObject = obj => obj && typeof obj === 'object' && !Array.isArray(obj);
const isConcatArray = opts && opts.concatArray;
let result = Object.assign({}, target);
if (isObject(target) && isObject(source)) {
for (const [sourceKey, sourceValue] of Object.entries(source)) {
const targetValue = target[sourceKey];
if (isConcatArray && Array.isArray(sourceValue) && Array.isArray(targetValue)) {
result[sourceKey] = targetValue.concat(...sourceValue);
}
else if (isObject(sourceValue) && target.hasOwnProperty(sourceKey)) {
result[sourceKey] = mergeDeeply(targetValue, sourceValue, opts);
}
else {
Object.assign(result, {[sourceKey]: sourceValue});
}
}
}
return result;
}
const a = {
familyName: '織田',
firstName: '吉法師',
address: '尾張',
sex: '男',
details: {
character: {favoriteTactics: '奇襲', favoriteWord: '天下布武', favoritePlace: '京'},
ownedCastle: ['清洲城']
}
};
const b = {
familyName: '織田',
firstName: '信長',
details: {
character: {favoriteTactics: '鉄砲活用'},
ownedCastle: ['岐阜城', '安土城']
}
};
const result2 = mergeDeeply(a, b);
console.log('result2=');
console.log(result2);
これを実行すると、以下のようになる。
result2=
{ familyName: '織田',
firstName: '信長',
address: '尾張',
sex: '男',
details:
{ character:
{ favoriteTactics: '鉄砲活用',
favoriteWord: '天下布武',
favoritePlace: '京' },
ownedCastle: [ '岐阜城', '安土城' ] } }
今度は変数aの内容に、変数bの内容がオブジェクト以下まで含めてマージされ、深いマージができた。
配列の取り扱い
プロパティの値が配列だった場合の挙動をどうするかを考える。
####●配列の要素(中身)は結合(concat)しない場合
↑の例では、
a.details.ownedCastleは配列(=['清洲城']
)であるが、
マージするときにこの配列とb.details.ownedCastle(=['岐阜城','安土城']
)は結合されない
####●配列の要素(中身)は結合(concat)したい場合
以下のようにして、さっきの関数を呼び出してやればOK
const result3 = mergeDeeply(a, b, {concatArray: true});
console.log('result3=');
console.log(result3);
result3=
{ familyName: '織田',
firstName: '信長',
address: '尾張',
sex: '男',
details:
{ character:
{ favoriteTactics: '鉄砲活用',
favoriteWord: '天下布武',
favoritePlace: '京' },
ownedCastle: [ '清洲城', '岐阜城', '安土城' ] } }
JSONどうしをマージする方法
JSONフォーマットのファイルや文字列として存在するJSONどうしをマージする方法も基本かわらない。いったんオブジェクトにしてから、戻すだけ。
const jsonA = `{"familyName":"織田","firstName":"吉法師","address":"尾張","sex":"男",
"details":{"character":{"favoriteTactics":"奇襲","favoriteWord":"天下布武","favoritePlace":"京"},
"ownedCastle":["清洲城"]}
}`;
const jsonB = `{"familyName":"織田","firstName":"信長",
"details":{"character":{"favoriteTactics":"鉄砲活用"},
"ownedCastle":["岐阜城","安土城"]}
}`;
const jsonResult = JSON.stringify(
mergeDeeply(JSON.parse(jsonA), JSON.parse(jsonB), {concatArray: true})
);
console.log('jsonResult=');
console.log(jsonResult);
jsonResult=
{"familyName":"織田","firstName":"信長","address":"尾張","sex":"男","details":{"character":{"favoriteTactics":"鉄砲活用","favoriteWord":"天下布武","favoritePlace":"京"},"ownedCastle":["清洲城","岐阜城","安土城"]}}
ライブラリを使う方法
深いマージ(deep merge)をするためのメジャーなライブラリも存在する
●インストール方法
npm install deepmerge
●使用方法
const merge = require('deepmerge');
merge(a, b)
まとめ
- Javascriptでオブジェクトをマージする方法について説明しました
- Object.assignをつかった浅いマージ、簡単な関数をつかった深いマージ、JSONどうしのマージ、ライブラリを使った方法などにふれました
参考・関連サイト
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals
- https://github.com/TehShrike/deepmerge
- https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge