【JSメモ】オブジェクトの差分
オブジェクトの差分検知
オブジェクトの差分を検知したいケースがあったので色々試してみた。
やりたいこととしては
- オブジェクトの処理前後でのプロパティを比較する
- 処理前後で「差分が発生しているか」ではなく、「差分の内容」を知りたい
- 差分の内容とは具体的には以下の通り
- 追加されたプロパティ
- 削除されたプロパティ
- 変更されたプロパティ(変更前後のプロパティの内容)
差分でいうとLodash
のomitBy
が真っ先に出てくるけど追加・削除されたプロパティの検知や
変更前後のプロパティを取得する用途には使えなさそうなので断念。
ということでオブジェクトのプロパティをチェックする関数を実装。
実装
function getObjectDiff(obj1, obj2) {
// 差分を格納するオブジェクト
const diff = {};
// 全てのキーをセット
const keys = new Set([...Object.keys(obj1), ...Object.keys(obj2)]);
for (const key of keys) {
const value1 = obj1[key];
const value2 = obj2[key];
if (typeof value1 === "object" && value1 !== null &&
typeof value2 === "object" && value2 !== null) {
// プロパティがオブジェクトの場合は再帰的に比較
const nestedDiff = getObjectDiff(value1, value2);
if (Object.keys(nestedDiff).length > 0) {
diff[key] = nestedDiff;
}
} else if (value1 !== value2) {
// 値が異なる場合は差分に追加
diff[key] = { old: value1, new: value2 };
}
}
return diff;
}
大まかな処理は以下の通り
- 最初に全てのキーを取得
- キーに対する値を取得して比較
- 値がオブジェクトの場合は再帰比較
- 値が異なる場合は前後の値を差分オブジェクトに追加
基本的な使い方
基本的な使い方や特徴は以下の通り。
- プロパティをキーに
old
とnew
で変更前後の値を持っている。- 削除追加された場合は
new
にundefined
が、 - 追加された場合は
old
にundefined
がセットされる。
- 削除追加された場合は
使用例
const obj1 = {
a: 1, // 変更なし
b: "2", // 変更あり
c: 3, // 削除
// d: 4 // 追加
};
const obj2 = {
a: 1,
b: "2b",
d: 4
};
const diff = getObjectDiff(obj1, obj2);
console.log(diff);
// 出力例:
// {
// b: { old: "2", new: "2b" },
// c: { old: 3, new: undefined },
// f: { old: undefined, new: 4 }
// }
複雑なオブジェクト
ネストされたプロパティやArrayの場合は以下のように差分を取得できる。
特に配列はindexごとに比較し、indexをキーに前後の値を検出している。
使用例
const obj1 = {
a1: [1, 2, 3, 4], // 配列の要素変更
a2: [1, 2, 3, 4], // 配列の要素削除
a3: [1, 2, 3, 4], // 配列の要素追加
a4: [1, 2, 3, 4], // 配列の要素の種類
b: {
b1: 1, // 変更なし
b2: 2, // 変更あり
b3: 3, // 削除
// b4: 4 // 追加
}
};
const obj2 = {
a1: [1, 2, 3, 5],
a2: [1, 2, 4],
a3: [1, 2, 3, 5, 5],
a4: ["1", "2", "3", "4"],
b: {
b1: 1,
b2: "2",
b4: 4,
}
};
const diff = getObjectDiff(obj1, obj2);
console.log(diff);
// 出力例:
// {
// a1: {
// 3: { old: 4, new: 5 }
// },
// a2: {
// 2: { old: 3, new: 4}
// 3: { old: 4, new: undefined}
// },
// a3: {
// 3: { old: 4, new: 5 }
// 4: { old: undefined, new: 4 }
// },
// a4: {
// 0: { old: 1, new: "1" },
// 1: { old: 2, new: "2" },
// 2: { old: 3, new: "3" },
// 3: { old: 4, new: "4" },
// },
// b: {
// b2: { old: 2, new: "2" },
// b3: { old: 3, new: undefined },
// b4: { old: undefined, new: 4 }
// }
// }