オブジェクトのコピー・マージ(shallow copy, deep copy)

オブジェクトのコピー、マージについて取り上げます。「shallow copy」「deep copy」などの違いを意識して実装する必要があります。
(動作検証はTypescriptで行っています。)

コピー

修正前|Objectは参照渡し

Objectは参照渡しになります。そのため、以下のように change関数 で変更した内容が objectA にも反映されます。

const output = (obj: {}) => {
  console.log(JSON.stringify(obj, null, 2));
};

const change = (obj: any) => {
  obj['xxx'] = 1;
};

const objectA = {
  aaa: 10,
  bbb: 5,
};

output(objectA);
// {
//   "aaa": 10,
//   "bbb": 5
// }

change(objectA);

output(objectA);
// {
//   "aaa": 10,
//   "bbb": 5,
//   "xxx": 1
// }

修正方法1|Object.assignでコピー

Object.assign を利用すると、Objectのコピーを作ることができます。

const output = (obj: {}) => {
  console.log(JSON.stringify(obj, null, 2));
};

const change = (obj: any) => {
  obj['xxx'] = 1;
};

const objectA = {
  aaa: 10,
  bbb: 5,
};

output(objectA);
// {
//   "aaa": 10,
//   "bbb": 5
// }

change(Object.assign({}, objectA));

output(objectA);
// {
//   "aaa": 10,
//   "bbb": 5,
// }

change関数 内ではコピーした別の実体を変更しています。そのため objectA 自体に変化が生じなくなりました。

修正方法2|スプレッド構文を利用

以下のようにスプレッド構文を利用して対応することも可能です。

const output = (obj: {}) => {
  console.log(JSON.stringify(obj, null, 2));
};

const change = (obj: any) => {
  obj['xxx'] = 1;
};

const objectA = {
  aaa: 10,
  bbb: 5,
};

output(objectA);
// {
//   "aaa": 10,
//   "bbb": 5
// }

change({ ...objectA });

output(objectA);
// {
//   "aaa": 10,
//   "bbb": 5,
// }

Object.assignでマージ

Object.assign は第1引数のオブジェクトに第2引数以降のオブジェクトをマージするといった処理を行っています。
(コピーを作成したい場合は 空のオブジェクト を第1引数に指定します。)

第1引数のオブジェクトに第2引数のオブジェクトをマージしてみます。

const output = (obj: object) => {
  console.log(JSON.stringify(obj, null, 2));
};

const objectA = {
  aaa: 10,
  bbb: 5,
};
const objectB = {
  aaa: 20,
  ccc: 3,
};

output(Object.assign(objectA, objectB));
// {
//   "aaa": 20,
//   "bbb": 5,
//   "ccc": 3
// }

output(objectA);
// {
//   "aaa": 20,
//   "bbb": 5,
//   "ccc": 3
// }

output(objectB);
// {
//   "aaa": 20,
//   "ccc": 3
// }

shallow copyとdeep copy

shallow copy

Object.assign を利用したコピーでは第1階層までしかコピーしてくれません。

const output = (obj: {}) => {
  console.log(JSON.stringify(obj, null, 2));
};

const change1 = (obj: any) => {
  obj['x'] = 'change';
};

const change2 = (obj: any) => {
  obj['x']['xx'] = 'change';
};

const objectA = {
  x: {
    xx: 1,
    yy: 2,
  },
};

output(objectA);
// {
//   "x": {
//     "xx": 1,
//     "yy": 2
//   }
// }

change1(Object.assign({}, objectA));
output(objectA);
// {
//   "x": {
//     "xx": 1,
//     "yy": 2
//   }
// }

change2(Object.assign({}, objectA));
output(objectA);
// {
//   "x": {
//     "xx": "change",
//     "yx": 2
//   }
// }

objectA['x']['xx'] の値が change に変わってしまいました。

deep copy

解決方法の1つとして lodashcloneDeep を利用するという方法があります。

import * as lodash from 'lodash';

const output = (obj: {}) => {
  console.log(JSON.stringify(obj, null, 2));
};

const change1 = (obj: any) => {
  obj['x'] = 'change';
};

const change2 = (obj: any) => {
  obj['x']['xx'] = 'change';
};

const objectA = {
  x: {
    xx: 1,
    yy: 2,
  },
};

output(objectA);
// {
//   "x": {
//     "xx": 1,
//     "yy": 2
//   }
// }

change1(lodash.cloneDeep(objectA));
output(objectA);
// {
//   "x": {
//     "xx": 1,
//     "yy": 2
//   }
// }

change2(lodash.cloneDeep(objectA));
output(objectA);
// {
//   "x": {
//     "xx": 1,
//     "yx": 2
//   }
// }

参考・関連