diff --git a/src/model.ts b/src/model.ts index 18b896c..595d83b 100644 --- a/src/model.ts +++ b/src/model.ts @@ -27,6 +27,7 @@ export type Subproperties = { previousValue: any; currentValue: any; status: DiffStatus; + subDiff?: Subproperties[]; }; export type ObjectDiff = { diff --git a/src/object-diff.ts b/src/object-diff.ts index 532a394..d5189ff 100644 --- a/src/object-diff.ts +++ b/src/object-diff.ts @@ -58,6 +58,52 @@ function formatSingleObjectDiff( }; } +function getPreviousMatch( + prevSubValues: [string, any][] | null, + nextSubProperty: any +): any | undefined { + if (!prevSubValues) { + return undefined; + } + const previousMatch = prevSubValues.find(([subPreviousKey]) => + isEqual(subPreviousKey, nextSubProperty) + ); + return previousMatch ? previousMatch[1] : undefined; +} + +function getSubPropertiesDiff( + previousValue: Record<string, any> | undefined, + nextValue: Record<string, any> +): Subproperties[] { + const subPropertiesDiff: Subproperties[] = []; + const prevSubValues = previousValue ? Object.entries(previousValue) : null; + let subDiff: Subproperties["subDiff"]; + Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => { + const previousMatch = getPreviousMatch(prevSubValues, nextSubProperty); + if (isObject(nextSubValue)) { + const data: Subproperties[] = getSubPropertiesDiff( + previousMatch, + nextSubValue + ); + if (data && data.length > 0) { + subDiff = data; + } + } + if (previousMatch) { + subPropertiesDiff.push({ + name: nextSubProperty, + previousValue: previousMatch, + currentValue: nextSubValue, + status: isEqual(previousMatch, nextSubValue) + ? STATUS.EQUAL + : STATUS.UPDATED, + ...(!!subDiff && { subDiff }), + }); + } + }); + return subPropertiesDiff; +} + export function getObjectDiff( prevData: ObjectData, nextData: ObjectData @@ -80,27 +126,10 @@ export function getObjectDiff( const previousValue = prevData[nextProperty]; if (isObject(nextValue)) { - const prevSubValues = previousValue - ? Object.entries(previousValue) - : null; - const subPropertiesDiff: Subproperties[] = []; - Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => { - if (prevSubValues) { - const previousMatch = prevSubValues.find(([subPreviousKey]) => - isEqual(subPreviousKey, nextSubProperty) - ); - if (previousMatch) { - subPropertiesDiff.push({ - name: nextSubProperty, - previousValue: previousMatch[1], - currentValue: nextSubValue, - status: isEqual(previousMatch[1], nextSubValue) - ? STATUS.EQUAL - : STATUS.UPDATED, - }); - } - } - }); + const subPropertiesDiff: Subproperties[] = getSubPropertiesDiff( + previousValue, + nextValue + ); const _status = subPropertiesDiff.some( (property) => property.status !== STATUS.EQUAL ) diff --git a/src/utils.ts b/src/utils.ts index 55681d5..c526893 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,9 @@ export function isEqual(a: any, b: any): boolean { if (typeof a !== typeof b) return false; if (Array.isArray(a)) { + if (a.length !== b.length) { + return false; + } return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i])); } if (typeof a === "object") { diff --git a/test/object-diff.test.ts b/test/object-diff.test.ts index 071c416..c561d9a 100644 --- a/test/object-diff.test.ts +++ b/test/object-diff.test.ts @@ -148,4 +148,131 @@ describe("getObjectDiff", () => { ], }); }); + it("detects changed between two deep nested objects", () => { + expect( + getObjectDiff( + { + id: 54, + user: { + name: "joe", + data: { + member: true, + hobbies: { + football: ["psg"], + rugby: ["france"], + }, + }, + }, + }, + { + id: 54, + user: { + name: "joe", + data: { + member: true, + hobbies: { + football: ["psg", "nantes"], + rugby: ["france"], + }, + }, + }, + } + ) + ).toStrictEqual({ + type: "object", + status: "updated", + diff: [ + { + property: "id", + previousValue: 54, + currentValue: 54, + status: "equal", + }, + { + property: "user", + previousValue: { + name: "joe", + data: { + member: true, + hobbies: { + football: ["psg"], + rugby: ["france"], + }, + }, + }, + currentValue: { + name: "joe", + data: { + member: true, + hobbies: { + football: ["psg", "nantes"], + rugby: ["france"], + }, + }, + }, + status: "updated", + subPropertiesDiff: [ + { + name: "name", + previousValue: "joe", + currentValue: "joe", + status: "equal", + }, + { + name: "data", + previousValue: { + member: true, + hobbies: { + football: ["psg"], + rugby: ["france"], + }, + }, + currentValue: { + member: true, + hobbies: { + football: ["psg", "nantes"], + rugby: ["france"], + }, + }, + status: "updated", + subDiff: [ + { + name: "member", + previousValue: true, + currentValue: true, + status: "equal", + }, + { + name: "hobbies", + previousValue: { + football: ["psg"], + rugby: ["france"], + }, + currentValue: { + football: ["psg", "nantes"], + rugby: ["france"], + }, + status: "updated", + subDiff: [ + { + name: "football", + previousValue: ["psg"], + currentValue: ["psg", "nantes"], + status: "updated", + }, + { + name: "rugby", + previousValue: ["france"], + currentValue: ["france"], + status: "equal", + }, + ], + }, + ], + }, + ], + }, + ], + }); + }); }); diff --git a/test/utils.test.ts b/test/utils.test.ts index b751386..babe049 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -38,6 +38,7 @@ describe("isEqual", () => { ] ) ).toBeFalsy(); + expect(isEqual(["psg"], ["psg", "nantes"])).toBeFalsy(); }); });