diff --git a/README.md b/README.md
index 92d9c59..45c3b0c 100644
--- a/README.md
+++ b/README.md
@@ -394,10 +394,35 @@ false;
More examples are availble in the tests of the source code.
+
+
+### OPTIONS
+
+`getObjectDiff()` and `isEqual()` accept a facultative `options` parameter:
+
+```ts
+{
+ discardArrayOrder?: boolean // false by default
+}
+```
+
+If `discardArrayOrder` is set to `true`, `["hello", "world"]` and `["world", "hello"]` will be considered as `equal`, because the two arrays have the same value, just not in the same order.
+
## CREDITS
DoneDeal0
+## SUPPORT
+
+If you use Superdiff, please show your support by buying me coffee:
+https://www.buymeacoffee.com/donedeal0
+
+
+
+
+
+
+
## CONTRIBUTING
Pull requests are welcome!
diff --git a/dist/index.d.ts b/dist/index.d.ts
index 198eef1..ad1b53b 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -1,6 +1,9 @@
type DiffStatus = "added" | "equal" | "moved" | "deleted" | "updated";
type ObjectData = Record | undefined | null;
type ListData = any;
+type Options = {
+ discardArrayOrder?: boolean;
+};
type ListDiff = {
type: "list";
status: DiffStatus;
@@ -31,11 +34,11 @@ type ObjectDiff = {
}[];
};
-declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData): ObjectDiff;
+declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, options?: Options): ObjectDiff;
declare const getListDiff: (prevList: ListData[] | undefined | null, nextList: ListData[] | undefined | null) => ListDiff;
-declare function isEqual(a: any, b: any): boolean;
+declare function isEqual(a: any, b: any, options?: Options): boolean;
declare function isObject(value: any): value is Record;
export { getListDiff, getObjectDiff, isEqual, isObject };
diff --git a/dist/index.js b/dist/index.js
index bf46d63..ba9fe3a 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -10,13 +10,18 @@ var STATUS = {
};
// src/utils.ts
-function isEqual(a, b) {
+function isEqual(a, b, options = { discardArrayOrder: false }) {
if (typeof a !== typeof b)
return false;
if (Array.isArray(a)) {
if (a.length !== b.length) {
return false;
}
+ if (options.discardArrayOrder) {
+ return a.every(
+ (v) => b.some((nextV) => JSON.stringify(nextV) === JSON.stringify(v))
+ );
+ }
return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i]));
}
if (typeof a === "object") {
@@ -73,17 +78,17 @@ function formatSingleObjectDiff(data, status) {
diff
};
}
-function getPreviousMatch(previousValue, nextSubProperty) {
+function getPreviousMatch(previousValue, nextSubProperty, options) {
if (!previousValue) {
return void 0;
}
const previousMatch = Object.entries(previousValue).find(
- ([subPreviousKey]) => isEqual(subPreviousKey, nextSubProperty)
+ ([subPreviousKey]) => isEqual(subPreviousKey, nextSubProperty, options)
);
return previousMatch ? previousMatch[1] : void 0;
}
-function getValueStatus(previousValue, nextValue) {
- if (isEqual(previousValue, nextValue)) {
+function getValueStatus(previousValue, nextValue, options) {
+ if (isEqual(previousValue, nextValue, options)) {
return STATUS.EQUAL;
}
return STATUS.UPDATED;
@@ -105,7 +110,7 @@ function getDeletedProperties(previousValue, nextValue) {
}
return void 0;
}
-function getSubPropertiesDiff(previousValue, nextValue) {
+function getSubPropertiesDiff(previousValue, nextValue, options) {
const subPropertiesDiff = [];
let subDiff;
const deletedMainSubProperties = getDeletedProperties(
@@ -123,7 +128,11 @@ function getSubPropertiesDiff(previousValue, nextValue) {
});
}
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
- const previousMatch = getPreviousMatch(previousValue, nextSubProperty);
+ const previousMatch = getPreviousMatch(
+ previousValue,
+ nextSubProperty,
+ options
+ );
if (!!!previousMatch && !!nextSubProperty) {
return subPropertiesDiff.push({
name: nextSubProperty,
@@ -135,7 +144,8 @@ function getSubPropertiesDiff(previousValue, nextValue) {
if (isObject(nextSubValue)) {
const data = getSubPropertiesDiff(
previousMatch,
- nextSubValue
+ nextSubValue,
+ options
);
if (data && data.length > 0) {
subDiff = data;
@@ -146,14 +156,14 @@ function getSubPropertiesDiff(previousValue, nextValue) {
name: nextSubProperty,
previousValue: previousMatch,
currentValue: nextSubValue,
- status: getValueStatus(previousMatch, nextSubValue),
+ status: getValueStatus(previousMatch, nextSubValue, options),
...!!subDiff && { subDiff }
});
}
});
return subPropertiesDiff;
}
-function getObjectDiff(prevData, nextData) {
+function getObjectDiff(prevData, nextData, options) {
if (!prevData && !nextData) {
return {
type: "object",
@@ -181,7 +191,8 @@ function getObjectDiff(prevData, nextData) {
if (isObject(nextValue)) {
const subPropertiesDiff = getSubPropertiesDiff(
previousValue,
- nextValue
+ nextValue,
+ options
);
return diff.push({
property: nextProperty,
@@ -195,7 +206,7 @@ function getObjectDiff(prevData, nextData) {
property: nextProperty,
previousValue,
currentValue: nextValue,
- status: getValueStatus(previousValue, nextValue)
+ status: getValueStatus(previousValue, nextValue, options)
});
});
const deletedProperties = getDeletedProperties(prevData, nextData);
diff --git a/dist/index.mjs b/dist/index.mjs
index fdb89dd..68ef8e2 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -8,13 +8,18 @@ var STATUS = {
};
// src/utils.ts
-function isEqual(a, b) {
+function isEqual(a, b, options = { discardArrayOrder: false }) {
if (typeof a !== typeof b)
return false;
if (Array.isArray(a)) {
if (a.length !== b.length) {
return false;
}
+ if (options.discardArrayOrder) {
+ return a.every(
+ (v) => b.some((nextV) => JSON.stringify(nextV) === JSON.stringify(v))
+ );
+ }
return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i]));
}
if (typeof a === "object") {
@@ -71,17 +76,17 @@ function formatSingleObjectDiff(data, status) {
diff
};
}
-function getPreviousMatch(previousValue, nextSubProperty) {
+function getPreviousMatch(previousValue, nextSubProperty, options) {
if (!previousValue) {
return void 0;
}
const previousMatch = Object.entries(previousValue).find(
- ([subPreviousKey]) => isEqual(subPreviousKey, nextSubProperty)
+ ([subPreviousKey]) => isEqual(subPreviousKey, nextSubProperty, options)
);
return previousMatch ? previousMatch[1] : void 0;
}
-function getValueStatus(previousValue, nextValue) {
- if (isEqual(previousValue, nextValue)) {
+function getValueStatus(previousValue, nextValue, options) {
+ if (isEqual(previousValue, nextValue, options)) {
return STATUS.EQUAL;
}
return STATUS.UPDATED;
@@ -103,7 +108,7 @@ function getDeletedProperties(previousValue, nextValue) {
}
return void 0;
}
-function getSubPropertiesDiff(previousValue, nextValue) {
+function getSubPropertiesDiff(previousValue, nextValue, options) {
const subPropertiesDiff = [];
let subDiff;
const deletedMainSubProperties = getDeletedProperties(
@@ -121,7 +126,11 @@ function getSubPropertiesDiff(previousValue, nextValue) {
});
}
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
- const previousMatch = getPreviousMatch(previousValue, nextSubProperty);
+ const previousMatch = getPreviousMatch(
+ previousValue,
+ nextSubProperty,
+ options
+ );
if (!!!previousMatch && !!nextSubProperty) {
return subPropertiesDiff.push({
name: nextSubProperty,
@@ -133,7 +142,8 @@ function getSubPropertiesDiff(previousValue, nextValue) {
if (isObject(nextSubValue)) {
const data = getSubPropertiesDiff(
previousMatch,
- nextSubValue
+ nextSubValue,
+ options
);
if (data && data.length > 0) {
subDiff = data;
@@ -144,14 +154,14 @@ function getSubPropertiesDiff(previousValue, nextValue) {
name: nextSubProperty,
previousValue: previousMatch,
currentValue: nextSubValue,
- status: getValueStatus(previousMatch, nextSubValue),
+ status: getValueStatus(previousMatch, nextSubValue, options),
...!!subDiff && { subDiff }
});
}
});
return subPropertiesDiff;
}
-function getObjectDiff(prevData, nextData) {
+function getObjectDiff(prevData, nextData, options) {
if (!prevData && !nextData) {
return {
type: "object",
@@ -179,7 +189,8 @@ function getObjectDiff(prevData, nextData) {
if (isObject(nextValue)) {
const subPropertiesDiff = getSubPropertiesDiff(
previousValue,
- nextValue
+ nextValue,
+ options
);
return diff.push({
property: nextProperty,
@@ -193,7 +204,7 @@ function getObjectDiff(prevData, nextData) {
property: nextProperty,
previousValue,
currentValue: nextValue,
- status: getValueStatus(previousValue, nextValue)
+ status: getValueStatus(previousValue, nextValue, options)
});
});
const deletedProperties = getDeletedProperties(prevData, nextData);
diff --git a/package.json b/package.json
index 9060cfb..93d29e3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@donedeal0/superdiff",
- "version": "1.0.4",
+ "version": "1.0.5",
"description": "SuperDiff checks the changes between two objects or arrays. It returns a complete diff with relevant information for each property or piece of data",
"main": "dist/index.js",
"module": "dist/index.js",
diff --git a/src/model.ts b/src/model.ts
index 595d83b..71bd953 100644
--- a/src/model.ts
+++ b/src/model.ts
@@ -9,6 +9,7 @@ export const STATUS: Record = {
export type DiffStatus = "added" | "equal" | "moved" | "deleted" | "updated";
export type ObjectData = Record | undefined | null;
export type ListData = any;
+export type Options = { discardArrayOrder?: boolean };
export type ListDiff = {
type: "list";
diff --git a/src/object-diff.ts b/src/object-diff.ts
index a64276a..cd2e83e 100644
--- a/src/object-diff.ts
+++ b/src/object-diff.ts
@@ -4,6 +4,7 @@ import {
DiffStatus,
STATUS,
Subproperties,
+ Options,
} from "./model";
import { isObject, isEqual } from "./utils";
@@ -60,19 +61,24 @@ function formatSingleObjectDiff(
function getPreviousMatch(
previousValue: any | undefined,
- nextSubProperty: any
+ nextSubProperty: any,
+ options?: Options
): any | undefined {
if (!previousValue) {
return undefined;
}
const previousMatch = Object.entries(previousValue).find(([subPreviousKey]) =>
- isEqual(subPreviousKey, nextSubProperty)
+ isEqual(subPreviousKey, nextSubProperty, options)
);
return previousMatch ? previousMatch[1] : undefined;
}
-function getValueStatus(previousValue: any, nextValue: any): DiffStatus {
- if (isEqual(previousValue, nextValue)) {
+function getValueStatus(
+ previousValue: any,
+ nextValue: any,
+ options?: Options
+): DiffStatus {
+ if (isEqual(previousValue, nextValue, options)) {
return STATUS.EQUAL;
}
return STATUS.UPDATED;
@@ -103,7 +109,8 @@ function getDeletedProperties(
function getSubPropertiesDiff(
previousValue: Record | undefined,
- nextValue: Record
+ nextValue: Record,
+ options?: Options
): Subproperties[] {
const subPropertiesDiff: Subproperties[] = [];
let subDiff: Subproperties[];
@@ -122,7 +129,11 @@ function getSubPropertiesDiff(
});
}
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
- const previousMatch = getPreviousMatch(previousValue, nextSubProperty);
+ const previousMatch = getPreviousMatch(
+ previousValue,
+ nextSubProperty,
+ options
+ );
if (!!!previousMatch && !!nextSubProperty) {
return subPropertiesDiff.push({
name: nextSubProperty,
@@ -134,7 +145,8 @@ function getSubPropertiesDiff(
if (isObject(nextSubValue)) {
const data: Subproperties[] = getSubPropertiesDiff(
previousMatch,
- nextSubValue
+ nextSubValue,
+ options
);
if (data && data.length > 0) {
subDiff = data;
@@ -145,7 +157,7 @@ function getSubPropertiesDiff(
name: nextSubProperty,
previousValue: previousMatch,
currentValue: nextSubValue,
- status: getValueStatus(previousMatch, nextSubValue),
+ status: getValueStatus(previousMatch, nextSubValue, options),
...(!!subDiff && { subDiff }),
});
}
@@ -155,7 +167,8 @@ function getSubPropertiesDiff(
export function getObjectDiff(
prevData: ObjectData,
- nextData: ObjectData
+ nextData: ObjectData,
+ options?: Options
): ObjectDiff {
if (!prevData && !nextData) {
return {
@@ -184,7 +197,8 @@ export function getObjectDiff(
if (isObject(nextValue)) {
const subPropertiesDiff: Subproperties[] = getSubPropertiesDiff(
previousValue,
- nextValue
+ nextValue,
+ options
);
return diff.push({
property: nextProperty,
@@ -198,7 +212,7 @@ export function getObjectDiff(
property: nextProperty,
previousValue,
currentValue: nextValue,
- status: getValueStatus(previousValue, nextValue),
+ status: getValueStatus(previousValue, nextValue, options),
});
});
const deletedProperties = getDeletedProperties(prevData, nextData);
diff --git a/src/utils.ts b/src/utils.ts
index c526893..be4d961 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,9 +1,20 @@
-export function isEqual(a: any, b: any): boolean {
+import { Options } from "./model";
+
+export function isEqual(
+ a: any,
+ b: any,
+ options: Options = { discardArrayOrder: false }
+): boolean {
if (typeof a !== typeof b) return false;
if (Array.isArray(a)) {
if (a.length !== b.length) {
return false;
}
+ if (options.discardArrayOrder) {
+ return a.every((v) =>
+ b.some((nextV: any) => JSON.stringify(nextV) === JSON.stringify(v))
+ );
+ }
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 00fa639..36cc1d7 100644
--- a/test/object-diff.test.ts
+++ b/test/object-diff.test.ts
@@ -301,4 +301,102 @@ describe("getObjectDiff", () => {
],
});
});
+ it("detects changed between two objects BUT doesn't care about array order as long as all values are preserved", () => {
+ expect(
+ getObjectDiff(
+ {
+ id: 54,
+ type: "sport",
+ user: {
+ name: "joe",
+ member: true,
+ hobbies: ["golf", "football"],
+ age: 66,
+ },
+ },
+ {
+ id: 54,
+ country: "us",
+ user: {
+ name: "joe",
+ member: false,
+ hobbies: ["football", "golf"],
+ nickname: "super joe",
+ },
+ },
+ { discardArrayOrder: true }
+ )
+ ).toStrictEqual({
+ type: "object",
+ status: "updated",
+ diff: [
+ {
+ property: "id",
+ previousValue: 54,
+ currentValue: 54,
+ status: "equal",
+ },
+ {
+ property: "country",
+ previousValue: undefined,
+ currentValue: "us",
+ status: "added",
+ },
+ {
+ property: "user",
+ previousValue: {
+ name: "joe",
+ member: true,
+ hobbies: ["golf", "football"],
+ age: 66,
+ },
+ currentValue: {
+ name: "joe",
+ member: false,
+ hobbies: ["football", "golf"],
+ nickname: "super joe",
+ },
+ status: "updated",
+ subPropertiesDiff: [
+ {
+ name: "age",
+ previousValue: 66,
+ currentValue: undefined,
+ status: "deleted",
+ },
+ {
+ name: "name",
+ previousValue: "joe",
+ currentValue: "joe",
+ status: "equal",
+ },
+ {
+ name: "member",
+ previousValue: true,
+ currentValue: false,
+ status: "updated",
+ },
+ {
+ name: "hobbies",
+ previousValue: ["golf", "football"],
+ currentValue: ["football", "golf"],
+ status: "equal",
+ },
+ {
+ name: "nickname",
+ previousValue: undefined,
+ currentValue: "super joe",
+ status: "added",
+ },
+ ],
+ },
+ {
+ property: "type",
+ previousValue: "sport",
+ currentValue: undefined,
+ status: "deleted",
+ },
+ ],
+ });
+ });
});