JavaScript のオブジェクトをディープマージしたい。

最終更新日:





JavaScript のオブジェクトをディープマージしたい。npm パッケージは存在するけど自力でやりたい。





求める結合結果


以下のように、定数 a と定数 b が定義してあったとする。

const a = {
  string: 'hello world',
  array: [10, 20, 30, 40],
  object: {
    boolean: true,
    array: ['foo', 'bar', 'baz'],
    number: 100,
  },
  number: 10,
};

const b = {
  string: 'Hello World',
  array: [30, 40, 50, 60],
  object: {
    array: [{string: 'hello'}],
    number: 500,
  },
};


求めている結合結果は以下のようなもので、配列は重複を除いて値が追加され、オブジェクトはどちらかに在るキー/値を残したい。

{
  string: 'Hello World',
  array: [ 10, 20, 30, 40, 50, 60 ],
  object: {
    boolean: true,
    array: [ 'foo', 'bar', 'baz', { string: 'hello' } ],
    number: 500
  },
  number: 10
}



Object.assign でのマージ


Object.assign を使ってマージすると全て上書きされる

const c = Object.assign(a, b);
console.log(c)

出力結果。a.arrayb.array の配列ので上書きされ、a.object.boolean はキーそのものがなくなる。これじゃない。

{
  string: 'Hello World',
  array: [ 30, 40, 50, 60 ],
  object: {
    array: [ { string: 'hello' } ],
    number: 500
  }
  number: 10,
}



deep merge するコード

配列の値の重複やソートなんかは、求める結果によって書き換える必要はあるけど、30行くらいで書ける。

function deepMerge(target, ...sources) {
  if (sources.length == 0) return target;

  const targetIsArray = Array.isArray(target);
  // 配列だったら [] オブジェクトだったら {} にする。
  const result = Object.assign(targetIsArray ? [] : {}, target);

  sources.forEach((source) => {
    for (const key in source) {
      const targetValue = result[key];
      const sourceValue = source[key];

      // objectだったら再起させる
      if (typeof targetValue === 'object' && typeof sourceValue === 'object') {
        result[key] = deepMerge(targetValue, sourceValue);
        continue;
      }

      // 配列だったら重複してなければ追加する
      if (targetIsArray) {
        if (!result.includes(sourceValue)) {
          result.push(sourceValue);
        }
        continue;
      }

      // 配列以外は値を上書きしていく
      result[key] = sourceValue;
    }
  });

  return result;
}

const a = {
  string: 'hello world',
  array: [10, 20, 30, 40],
  object: {
    boolean: true,
    array: ['foo', 'bar', 'baz'],
    number: 100,
  },
  number: 10,
};

const b = {
  string: 'Hello World',
  array: [30, 40, 50, 60],
  object: {
    array: [{string: 'hello'}],
    number: 500,
  },
};

const c = deepMerge(a, b);
console.log(c);


実行結果

{
  string: 'Hello World',
  array: [ 10, 20, 30, 40, 50, 60 ],
  object: {
    boolean: true,
    array: [ 'foo', 'bar', 'baz', {string: 'hello'} ],
    number: 500
  },
  number: 10
}



コメント