Skip to content

Commit 3e95fe5

Browse files
author
DoneDeal0
authored
Merge pull request #1 from DoneDeal0/deep-nested-diff
feat: start deep nested diff
2 parents f3b9fe3 + ec1c716 commit 3e95fe5

File tree

5 files changed

+182
-21
lines changed

5 files changed

+182
-21
lines changed

src/model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type Subproperties = {
2727
previousValue: any;
2828
currentValue: any;
2929
status: DiffStatus;
30+
subDiff?: Subproperties[];
3031
};
3132

3233
export type ObjectDiff = {

src/object-diff.ts

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,52 @@ function formatSingleObjectDiff(
5858
};
5959
}
6060

61+
function getPreviousMatch(
62+
prevSubValues: [string, any][] | null,
63+
nextSubProperty: any
64+
): any | undefined {
65+
if (!prevSubValues) {
66+
return undefined;
67+
}
68+
const previousMatch = prevSubValues.find(([subPreviousKey]) =>
69+
isEqual(subPreviousKey, nextSubProperty)
70+
);
71+
return previousMatch ? previousMatch[1] : undefined;
72+
}
73+
74+
function getSubPropertiesDiff(
75+
previousValue: Record<string, any> | undefined,
76+
nextValue: Record<string, any>
77+
): Subproperties[] {
78+
const subPropertiesDiff: Subproperties[] = [];
79+
const prevSubValues = previousValue ? Object.entries(previousValue) : null;
80+
let subDiff: Subproperties["subDiff"];
81+
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
82+
const previousMatch = getPreviousMatch(prevSubValues, nextSubProperty);
83+
if (isObject(nextSubValue)) {
84+
const data: Subproperties[] = getSubPropertiesDiff(
85+
previousMatch,
86+
nextSubValue
87+
);
88+
if (data && data.length > 0) {
89+
subDiff = data;
90+
}
91+
}
92+
if (previousMatch) {
93+
subPropertiesDiff.push({
94+
name: nextSubProperty,
95+
previousValue: previousMatch,
96+
currentValue: nextSubValue,
97+
status: isEqual(previousMatch, nextSubValue)
98+
? STATUS.EQUAL
99+
: STATUS.UPDATED,
100+
...(!!subDiff && { subDiff }),
101+
});
102+
}
103+
});
104+
return subPropertiesDiff;
105+
}
106+
61107
export function getObjectDiff(
62108
prevData: ObjectData,
63109
nextData: ObjectData
@@ -80,27 +126,10 @@ export function getObjectDiff(
80126
const previousValue = prevData[nextProperty];
81127

82128
if (isObject(nextValue)) {
83-
const prevSubValues = previousValue
84-
? Object.entries(previousValue)
85-
: null;
86-
const subPropertiesDiff: Subproperties[] = [];
87-
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
88-
if (prevSubValues) {
89-
const previousMatch = prevSubValues.find(([subPreviousKey]) =>
90-
isEqual(subPreviousKey, nextSubProperty)
91-
);
92-
if (previousMatch) {
93-
subPropertiesDiff.push({
94-
name: nextSubProperty,
95-
previousValue: previousMatch[1],
96-
currentValue: nextSubValue,
97-
status: isEqual(previousMatch[1], nextSubValue)
98-
? STATUS.EQUAL
99-
: STATUS.UPDATED,
100-
});
101-
}
102-
}
103-
});
129+
const subPropertiesDiff: Subproperties[] = getSubPropertiesDiff(
130+
previousValue,
131+
nextValue
132+
);
104133
const _status = subPropertiesDiff.some(
105134
(property) => property.status !== STATUS.EQUAL
106135
)

src/utils.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
export function isEqual(a: any, b: any): boolean {
22
if (typeof a !== typeof b) return false;
33
if (Array.isArray(a)) {
4+
if (a.length !== b.length) {
5+
return false;
6+
}
47
return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i]));
58
}
69
if (typeof a === "object") {

test/object-diff.test.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,131 @@ describe("getObjectDiff", () => {
148148
],
149149
});
150150
});
151+
it("detects changed between two deep nested objects", () => {
152+
expect(
153+
getObjectDiff(
154+
{
155+
id: 54,
156+
user: {
157+
name: "joe",
158+
data: {
159+
member: true,
160+
hobbies: {
161+
football: ["psg"],
162+
rugby: ["france"],
163+
},
164+
},
165+
},
166+
},
167+
{
168+
id: 54,
169+
user: {
170+
name: "joe",
171+
data: {
172+
member: true,
173+
hobbies: {
174+
football: ["psg", "nantes"],
175+
rugby: ["france"],
176+
},
177+
},
178+
},
179+
}
180+
)
181+
).toStrictEqual({
182+
type: "object",
183+
status: "updated",
184+
diff: [
185+
{
186+
property: "id",
187+
previousValue: 54,
188+
currentValue: 54,
189+
status: "equal",
190+
},
191+
{
192+
property: "user",
193+
previousValue: {
194+
name: "joe",
195+
data: {
196+
member: true,
197+
hobbies: {
198+
football: ["psg"],
199+
rugby: ["france"],
200+
},
201+
},
202+
},
203+
currentValue: {
204+
name: "joe",
205+
data: {
206+
member: true,
207+
hobbies: {
208+
football: ["psg", "nantes"],
209+
rugby: ["france"],
210+
},
211+
},
212+
},
213+
status: "updated",
214+
subPropertiesDiff: [
215+
{
216+
name: "name",
217+
previousValue: "joe",
218+
currentValue: "joe",
219+
status: "equal",
220+
},
221+
{
222+
name: "data",
223+
previousValue: {
224+
member: true,
225+
hobbies: {
226+
football: ["psg"],
227+
rugby: ["france"],
228+
},
229+
},
230+
currentValue: {
231+
member: true,
232+
hobbies: {
233+
football: ["psg", "nantes"],
234+
rugby: ["france"],
235+
},
236+
},
237+
status: "updated",
238+
subDiff: [
239+
{
240+
name: "member",
241+
previousValue: true,
242+
currentValue: true,
243+
status: "equal",
244+
},
245+
{
246+
name: "hobbies",
247+
previousValue: {
248+
football: ["psg"],
249+
rugby: ["france"],
250+
},
251+
currentValue: {
252+
football: ["psg", "nantes"],
253+
rugby: ["france"],
254+
},
255+
status: "updated",
256+
subDiff: [
257+
{
258+
name: "football",
259+
previousValue: ["psg"],
260+
currentValue: ["psg", "nantes"],
261+
status: "updated",
262+
},
263+
{
264+
name: "rugby",
265+
previousValue: ["france"],
266+
currentValue: ["france"],
267+
status: "equal",
268+
},
269+
],
270+
},
271+
],
272+
},
273+
],
274+
},
275+
],
276+
});
277+
});
151278
});

test/utils.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ describe("isEqual", () => {
3838
]
3939
)
4040
).toBeFalsy();
41+
expect(isEqual(["psg"], ["psg", "nantes"])).toBeFalsy();
4142
});
4243
});
4344

0 commit comments

Comments
 (0)