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..3893619 100644 --- a/src/object-diff.ts +++ b/src/object-diff.ts @@ -58,6 +58,53 @@ function formatSingleObjectDiff( }; } +function getPreviousMatch( + prevSubValues: [string, any][] | null, + nextSubProperty: any +): any | undefined { + const previousMatch = + prevSubValues && + prevSubValues.find(([subPreviousKey]) => + isEqual(subPreviousKey, nextSubProperty) + ); + return previousMatch ? previousMatch[1] : undefined; +} + +function getSubPropertiesDiff( + previousValue: Record | undefined, + nextValue: Record +): 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 (prevSubValues) { + 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 +127,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/test/object-diff.test.ts b/test/object-diff.test.ts index 071c416..a65b170 100644 --- a/test/object-diff.test.ts +++ b/test/object-diff.test.ts @@ -1,95 +1,215 @@ import { getObjectDiff } from "../src/object-diff"; describe("getObjectDiff", () => { - it("returns an empty diff if no objects are provided", () => { - expect(getObjectDiff(null, null)).toStrictEqual({ - type: "object", - status: "equal", - diff: [], - }); - }); - it("consider previous object as completely deleted if no next object is provided", () => { - expect( - getObjectDiff( - { name: "joe", age: 54, hobbies: ["golf", "football"] }, - null + // it("returns an empty diff if no objects are provided", () => { + // expect(getObjectDiff(null, null)).toStrictEqual({ + // type: "object", + // status: "equal", + // diff: [], + // }); + // }); + // it("consider previous object as completely deleted if no next object is provided", () => { + // expect( + // getObjectDiff( + // { name: "joe", age: 54, hobbies: ["golf", "football"] }, + // null + // ) + // ).toStrictEqual({ + // type: "object", + // status: "deleted", + // diff: [ + // { + // property: "name", + // previousValue: "joe", + // currentValue: undefined, + // status: "deleted", + // }, + // { + // property: "age", + // previousValue: 54, + // currentValue: undefined, + // status: "deleted", + // }, + // { + // property: "hobbies", + // previousValue: ["golf", "football"], + // currentValue: undefined, + // status: "deleted", + // }, + // ], + // }); + // }); + // it("consider next object as completely added if no previous object is provided", () => { + // expect( + // getObjectDiff(null, { + // name: "joe", + // age: 54, + // hobbies: ["golf", "football"], + // }) + // ).toStrictEqual({ + // type: "object", + // status: "added", + // diff: [ + // { + // property: "name", + // previousValue: undefined, + // currentValue: "joe", + // status: "added", + // }, + // { + // property: "age", + // previousValue: undefined, + // currentValue: 54, + // status: "added", + // }, + // { + // property: "hobbies", + // previousValue: undefined, + // currentValue: ["golf", "football"], + // status: "added", + // }, + // ], + // }); + // }); + // it("detects changed between two objects", () => { + // expect( + // getObjectDiff( + // { + // id: 54, + // user: { + // name: "joe", + // member: true, + // hobbies: ["golf", "football"], + // age: 66, + // }, + // }, + // { + // id: 54, + // user: { + // name: "joe", + // member: false, + // hobbies: ["golf", "chess"], + // age: 66, + // }, + // } + // ) + // ).toStrictEqual({ + // type: "object", + // status: "updated", + // diff: [ + // { + // property: "id", + // previousValue: 54, + // currentValue: 54, + // status: "equal", + // }, + // { + // property: "user", + // previousValue: { + // name: "joe", + // member: true, + // hobbies: ["golf", "football"], + // age: 66, + // }, + // currentValue: { + // name: "joe", + // member: false, + // hobbies: ["golf", "chess"], + // age: 66, + // }, + // status: "updated", + // subPropertiesDiff: [ + // { + // name: "name", + // previousValue: "joe", + // currentValue: "joe", + // status: "equal", + // }, + // { + // name: "member", + // previousValue: true, + // currentValue: false, + // status: "updated", + // }, + // { + // name: "hobbies", + // previousValue: ["golf", "football"], + // currentValue: ["golf", "chess"], + // status: "updated", + // }, + // { + // name: "age", + // previousValue: 66, + // currentValue: 66, + // status: "equal", + // }, + // ], + // }, + // ], + // }); + // }); + it("detects changed between two deep nested objects", () => { + console.log( + "res", + JSON.stringify( + 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"], + }, + }, + }, + } + ), + null, + 2 ) - ).toStrictEqual({ - type: "object", - status: "deleted", - diff: [ - { - property: "name", - previousValue: "joe", - currentValue: undefined, - status: "deleted", - }, - { - property: "age", - previousValue: 54, - currentValue: undefined, - status: "deleted", - }, - { - property: "hobbies", - previousValue: ["golf", "football"], - currentValue: undefined, - status: "deleted", - }, - ], - }); - }); - it("consider next object as completely added if no previous object is provided", () => { - expect( - getObjectDiff(null, { - name: "joe", - age: 54, - hobbies: ["golf", "football"], - }) - ).toStrictEqual({ - type: "object", - status: "added", - diff: [ - { - property: "name", - previousValue: undefined, - currentValue: "joe", - status: "added", - }, - { - property: "age", - previousValue: undefined, - currentValue: 54, - status: "added", - }, - { - property: "hobbies", - previousValue: undefined, - currentValue: ["golf", "football"], - status: "added", - }, - ], - }); - }); - it("detects changed between two objects", () => { + ); expect( getObjectDiff( { id: 54, user: { name: "joe", - member: true, - hobbies: ["golf", "football"], - age: 66, + data: { + member: true, + hobbies: { + football: ["psg"], + rugby: ["france"], + }, + }, }, }, { id: 54, user: { name: "joe", - member: false, - hobbies: ["golf", "chess"], - age: 66, + data: { + member: true, + hobbies: { + football: ["psg", "nantes"], + rugby: ["france"], + }, + }, }, } ) @@ -107,15 +227,23 @@ describe("getObjectDiff", () => { property: "user", previousValue: { name: "joe", - member: true, - hobbies: ["golf", "football"], - age: 66, + data: { + member: true, + hobbies: { + football: ["psg"], + rugby: ["france"], + }, + }, }, currentValue: { name: "joe", - member: false, - hobbies: ["golf", "chess"], - age: 66, + data: { + member: true, + hobbies: { + football: ["psg", "nantes"], + rugby: ["france"], + }, + }, }, status: "updated", subPropertiesDiff: [ @@ -126,22 +254,56 @@ describe("getObjectDiff", () => { status: "equal", }, { - name: "member", - previousValue: true, - currentValue: false, + name: "data", + previousValue: { + member: true, + hobbies: { + football: ["psg"], + rugby: ["france"], + }, + }, + currentValue: { + member: true, + hobbies: { + football: ["psg", "nantes"], + rugby: ["france"], + }, + }, status: "updated", - }, - { - name: "hobbies", - previousValue: ["golf", "football"], - currentValue: ["golf", "chess"], - status: "updated", - }, - { - name: "age", - previousValue: 66, - currentValue: 66, - status: "equal", + 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", // error the algo says it's equal... + }, + { + name: "rugby", + previousValue: ["france"], + currentValue: ["france"], + status: "equal", + }, + ], + }, + ], }, ], },