😇

lodash.mergeとdeepmergeの挙動の違いにハマった話

2021/06/29に公開

TLDR

  • deepmergeとlodash.mergeは、クラスのインスタンス等の特殊なオブジェクトの取り扱い方が異なる
  • deepmergeはデフォルト設定だとクラスインスタンスの同一性を破壊する
  • deepmergeとlodash.mergeを同一の挙動にするには、isMergeableObjectを使う

どういうこと?

https://stackblitz.com/edit/typescript-l2rffj

import moment from 'moment';
import merge from 'deepmerge';
import { isPlainObject } from 'is-plain-object';
import { merge as lodashMerge, cloneDeep } from 'lodash-es';

const base = {
  a: 'hoge',
  b: 5,
  c: true,
  d: null
};

const another = {
  d: moment()
};

const mergedA = merge(cloneDeep(base), cloneDeep(another));
const mergedB = lodashMerge(cloneDeep(base), cloneDeep(another));
const mergedC = merge(cloneDeep(base), cloneDeep(another), {
  isMergeableObject: isPlainObject
});

console.log({ mergedA, mergedB, mergedC });

deepmergeはあらゆるオブジェクトを「プレーンなオブジェクトとして」取り扱うため、マージしたいオブジェクトのプロパティにクラスインスタンスなどが指定されていると、その中身がプレーンなオブジェクトに変換されてしまいます。

参考例では、momentのインスタンスをdプロパティに指定したオブジェクトをマージしていますが、deepmergeを使用したmergedAはただのObjectとして表示される一方、lodash.mergeを使用したmergedBではインスタンスが維持されたままになっています。

deepmergeで同様の挙動を実現するには、オプションにisMergeableObject: isPlainObjectを指定し、プレーンなオブジェクトのみをマージ対象にする(?)必要があります。
これを指定したmergedCでは、mergedBと同じ表示になっていることが確認できます。

lodashからの脱却を図る際、deepmergeは有力な代替ライブラリとなり得ますが、このような挙動の差があるため、差し替える際には十分に注意が必要です、という話でした。(2時間を失いました)

Discussion