Browse Source

Merge pull request #1 from DoneDeal0/deep-nested-diff

feat: start deep nested diff
pull/6/head
DoneDeal0 2 years ago committed by GitHub
parent
commit
3e95fe5bae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/model.ts
  2. 71
      src/object-diff.ts
  3. 3
      src/utils.ts
  4. 127
      test/object-diff.test.ts
  5. 1
      test/utils.test.ts

1
src/model.ts

@ -27,6 +27,7 @@ export type Subproperties = {
previousValue: any; previousValue: any;
currentValue: any; currentValue: any;
status: DiffStatus; status: DiffStatus;
subDiff?: Subproperties[];
}; };
export type ObjectDiff = { export type ObjectDiff = {

71
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( export function getObjectDiff(
prevData: ObjectData, prevData: ObjectData,
nextData: ObjectData nextData: ObjectData
@ -80,27 +126,10 @@ export function getObjectDiff(
const previousValue = prevData[nextProperty]; const previousValue = prevData[nextProperty];
if (isObject(nextValue)) { if (isObject(nextValue)) {
const prevSubValues = previousValue const subPropertiesDiff: Subproperties[] = getSubPropertiesDiff(
? Object.entries(previousValue) previousValue,
: null; nextValue
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 _status = subPropertiesDiff.some( const _status = subPropertiesDiff.some(
(property) => property.status !== STATUS.EQUAL (property) => property.status !== STATUS.EQUAL
) )

3
src/utils.ts

@ -1,6 +1,9 @@
export function isEqual(a: any, b: any): boolean { export function isEqual(a: any, b: any): boolean {
if (typeof a !== typeof b) return false; if (typeof a !== typeof b) return false;
if (Array.isArray(a)) { if (Array.isArray(a)) {
if (a.length !== b.length) {
return false;
}
return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i])); return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i]));
} }
if (typeof a === "object") { if (typeof a === "object") {

127
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",
},
],
},
],
},
],
},
],
});
});
}); });

1
test/utils.test.ts

@ -38,6 +38,7 @@ describe("isEqual", () => {
] ]
) )
).toBeFalsy(); ).toBeFalsy();
expect(isEqual(["psg"], ["psg", "nantes"])).toBeFalsy();
}); });
}); });

Loading…
Cancel
Save