JavaScriptでオブジェクトをマージ（結合）する方法、JSONのマージをする方法

# 概要
**JavaScriptで以下のようなオブジェクトをマージする方法について**書きます。

本題は<font color=blue>**深いマージ(deep merge,deep copy)**</font>です

※functionが存在しないプロパティのみのオブジェクトを想定

```javascript
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](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)をつかうと以下のようになる。

```javascript:Object.assingを使う例

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);
```

ちなみに、[スプレッド構文](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals)をつかうと、**Object.assign**よりも短い記述で書ける

```javascript:スプレッド構文をつかった例
const result1= { ...a, ...b };
```

```javascript:結果
result1=
{ familyName: '織田',
  firstName: '信長',
  address: '尾張',
  sex: '男',
  details: 
   { character: { favoriteTactics: '鉄砲活用' },
     ownedCastle: [ '岐阜城', '安土城' ] } }
```

[Object.assign](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/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)をできるようにする。


```javascript
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);

```

これを実行すると、以下のようになる。

```javascript:結果
result2=
{ familyName: '織田',
  firstName: '信長',
  address: '尾張',
  sex: '男',
  details: 
   { character: 
      { favoriteTactics: '鉄砲活用',
        favoriteWord: '天下布武',
        favoritePlace: '京' },
     ownedCastle: [ '岐阜城', '安土城' ] } }
```

今度は変数**a**の内容に、変数**b**の内容がオブジェクト以下まで含めてマージされ、深いマージができた。

### 配列の取り扱い

プロパティの値が配列だった場合の挙動をどうするかを考える。

####●**配列の要素（中身）は結合(concat)<font color=red>しない</font>場合**

↑の例では、
**a.details.ownedCastle**は配列(＝`['清洲城']`)であるが、
マージするときにこの配列と**b.details.ownedCastle**（＝`['岐阜城','安土城']`)は結合されない

####●**配列の要素（中身）は結合(concat)<font color=blue>したい</font>場合**

以下のようにして、さっきの関数を呼び出してやればOK

```javascript:配列の中身を結合(concat)する
const result3 = mergeDeeply(a, b, {concatArray: true});

console.log('result3=');
console.log(result3);
```

```javascript:結果
result3=
{ familyName: '織田',
  firstName: '信長',
  address: '尾張',
  sex: '男',
  details: 
   { character: 
      { favoriteTactics: '鉄砲活用',
        favoriteWord: '天下布武',
        favoritePlace: '京' },
     ownedCastle: [ '清洲城', '岐阜城', '安土城' ] } }

```

# JSONどうしをマージする方法
JSONフォーマットのファイルや文字列として存在するJSONどうしをマージする方法も基本かわらない。いったんオブジェクトにしてから、戻すだけ。

```javascript
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);
```

```javascript:結果
jsonResult=
{"familyName":"織田","firstName":"信長","address":"尾張","sex":"男","details":{"character":{"favoriteTactics":"鉄砲活用","favoriteWord":"天下布武","favoritePlace":"京"},"ownedCastle":["清洲城","岐阜城","安土城"]}}

```


# ライブラリを使う方法

深いマージ(deep merge)をするためのメジャーなライブラリも存在する

https://www.npmjs.com/package/deepmerge

●インストール方法

```
npm install deepmerge
```

●使用方法

```javascript
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