Browse Source

feat: add buymeacoffee link + discardArrayOrder option

pull/6/head v1.0.5
DoneDeal0 2 years ago
parent
commit
fee7ae6f7b
  1. 25
      README.md
  2. 7
      dist/index.d.ts
  3. 35
      dist/index.js
  4. 35
      dist/index.mjs
  5. 2
      package.json
  6. 1
      src/model.ts
  7. 36
      src/object-diff.ts
  8. 13
      src/utils.ts
  9. 98
      test/object-diff.test.ts

25
README.md

@ -394,10 +394,35 @@ false;
More examples are availble in the tests of the source code. More examples are availble in the tests of the source code.
<hr/>
### 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 ## CREDITS
DoneDeal0 DoneDeal0
## SUPPORT
If you use Superdiff, please show your support by buying me coffee:
https://www.buymeacoffee.com/donedeal0
<br/>
<a href="https://www.buymeacoffee.com/donedeal0" target="_blank">
<img src="https://user-images.githubusercontent.com/43271780/178990049-46b05704-1344-4d55-a5a7-7265724edc5c.png"/>
</a>
<br/>
## CONTRIBUTING ## CONTRIBUTING
Pull requests are welcome! Pull requests are welcome!

7
dist/index.d.ts vendored

@ -1,6 +1,9 @@
type DiffStatus = "added" | "equal" | "moved" | "deleted" | "updated"; type DiffStatus = "added" | "equal" | "moved" | "deleted" | "updated";
type ObjectData = Record<string, any> | undefined | null; type ObjectData = Record<string, any> | undefined | null;
type ListData = any; type ListData = any;
type Options = {
discardArrayOrder?: boolean;
};
type ListDiff = { type ListDiff = {
type: "list"; type: "list";
status: DiffStatus; 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 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<string, any>; declare function isObject(value: any): value is Record<string, any>;
export { getListDiff, getObjectDiff, isEqual, isObject }; export { getListDiff, getObjectDiff, isEqual, isObject };

35
dist/index.js vendored

@ -10,13 +10,18 @@ var STATUS = {
}; };
// src/utils.ts // src/utils.ts
function isEqual(a, b) { function isEqual(a, b, options = { discardArrayOrder: false }) {
if (typeof a !== typeof b) if (typeof a !== typeof b)
return false; return false;
if (Array.isArray(a)) { if (Array.isArray(a)) {
if (a.length !== b.length) { if (a.length !== b.length) {
return false; 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])); return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i]));
} }
if (typeof a === "object") { if (typeof a === "object") {
@ -73,17 +78,17 @@ function formatSingleObjectDiff(data, status) {
diff diff
}; };
} }
function getPreviousMatch(previousValue, nextSubProperty) { function getPreviousMatch(previousValue, nextSubProperty, options) {
if (!previousValue) { if (!previousValue) {
return void 0; return void 0;
} }
const previousMatch = Object.entries(previousValue).find( const previousMatch = Object.entries(previousValue).find(
([subPreviousKey]) => isEqual(subPreviousKey, nextSubProperty) ([subPreviousKey]) => isEqual(subPreviousKey, nextSubProperty, options)
); );
return previousMatch ? previousMatch[1] : void 0; return previousMatch ? previousMatch[1] : void 0;
} }
function getValueStatus(previousValue, nextValue) { function getValueStatus(previousValue, nextValue, options) {
if (isEqual(previousValue, nextValue)) { if (isEqual(previousValue, nextValue, options)) {
return STATUS.EQUAL; return STATUS.EQUAL;
} }
return STATUS.UPDATED; return STATUS.UPDATED;
@ -105,7 +110,7 @@ function getDeletedProperties(previousValue, nextValue) {
} }
return void 0; return void 0;
} }
function getSubPropertiesDiff(previousValue, nextValue) { function getSubPropertiesDiff(previousValue, nextValue, options) {
const subPropertiesDiff = []; const subPropertiesDiff = [];
let subDiff; let subDiff;
const deletedMainSubProperties = getDeletedProperties( const deletedMainSubProperties = getDeletedProperties(
@ -123,7 +128,11 @@ function getSubPropertiesDiff(previousValue, nextValue) {
}); });
} }
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => { Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
const previousMatch = getPreviousMatch(previousValue, nextSubProperty); const previousMatch = getPreviousMatch(
previousValue,
nextSubProperty,
options
);
if (!!!previousMatch && !!nextSubProperty) { if (!!!previousMatch && !!nextSubProperty) {
return subPropertiesDiff.push({ return subPropertiesDiff.push({
name: nextSubProperty, name: nextSubProperty,
@ -135,7 +144,8 @@ function getSubPropertiesDiff(previousValue, nextValue) {
if (isObject(nextSubValue)) { if (isObject(nextSubValue)) {
const data = getSubPropertiesDiff( const data = getSubPropertiesDiff(
previousMatch, previousMatch,
nextSubValue nextSubValue,
options
); );
if (data && data.length > 0) { if (data && data.length > 0) {
subDiff = data; subDiff = data;
@ -146,14 +156,14 @@ function getSubPropertiesDiff(previousValue, nextValue) {
name: nextSubProperty, name: nextSubProperty,
previousValue: previousMatch, previousValue: previousMatch,
currentValue: nextSubValue, currentValue: nextSubValue,
status: getValueStatus(previousMatch, nextSubValue), status: getValueStatus(previousMatch, nextSubValue, options),
...!!subDiff && { subDiff } ...!!subDiff && { subDiff }
}); });
} }
}); });
return subPropertiesDiff; return subPropertiesDiff;
} }
function getObjectDiff(prevData, nextData) { function getObjectDiff(prevData, nextData, options) {
if (!prevData && !nextData) { if (!prevData && !nextData) {
return { return {
type: "object", type: "object",
@ -181,7 +191,8 @@ function getObjectDiff(prevData, nextData) {
if (isObject(nextValue)) { if (isObject(nextValue)) {
const subPropertiesDiff = getSubPropertiesDiff( const subPropertiesDiff = getSubPropertiesDiff(
previousValue, previousValue,
nextValue nextValue,
options
); );
return diff.push({ return diff.push({
property: nextProperty, property: nextProperty,
@ -195,7 +206,7 @@ function getObjectDiff(prevData, nextData) {
property: nextProperty, property: nextProperty,
previousValue, previousValue,
currentValue: nextValue, currentValue: nextValue,
status: getValueStatus(previousValue, nextValue) status: getValueStatus(previousValue, nextValue, options)
}); });
}); });
const deletedProperties = getDeletedProperties(prevData, nextData); const deletedProperties = getDeletedProperties(prevData, nextData);

35
dist/index.mjs vendored

@ -8,13 +8,18 @@ var STATUS = {
}; };
// src/utils.ts // src/utils.ts
function isEqual(a, b) { function isEqual(a, b, options = { discardArrayOrder: false }) {
if (typeof a !== typeof b) if (typeof a !== typeof b)
return false; return false;
if (Array.isArray(a)) { if (Array.isArray(a)) {
if (a.length !== b.length) { if (a.length !== b.length) {
return false; 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])); return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i]));
} }
if (typeof a === "object") { if (typeof a === "object") {
@ -71,17 +76,17 @@ function formatSingleObjectDiff(data, status) {
diff diff
}; };
} }
function getPreviousMatch(previousValue, nextSubProperty) { function getPreviousMatch(previousValue, nextSubProperty, options) {
if (!previousValue) { if (!previousValue) {
return void 0; return void 0;
} }
const previousMatch = Object.entries(previousValue).find( const previousMatch = Object.entries(previousValue).find(
([subPreviousKey]) => isEqual(subPreviousKey, nextSubProperty) ([subPreviousKey]) => isEqual(subPreviousKey, nextSubProperty, options)
); );
return previousMatch ? previousMatch[1] : void 0; return previousMatch ? previousMatch[1] : void 0;
} }
function getValueStatus(previousValue, nextValue) { function getValueStatus(previousValue, nextValue, options) {
if (isEqual(previousValue, nextValue)) { if (isEqual(previousValue, nextValue, options)) {
return STATUS.EQUAL; return STATUS.EQUAL;
} }
return STATUS.UPDATED; return STATUS.UPDATED;
@ -103,7 +108,7 @@ function getDeletedProperties(previousValue, nextValue) {
} }
return void 0; return void 0;
} }
function getSubPropertiesDiff(previousValue, nextValue) { function getSubPropertiesDiff(previousValue, nextValue, options) {
const subPropertiesDiff = []; const subPropertiesDiff = [];
let subDiff; let subDiff;
const deletedMainSubProperties = getDeletedProperties( const deletedMainSubProperties = getDeletedProperties(
@ -121,7 +126,11 @@ function getSubPropertiesDiff(previousValue, nextValue) {
}); });
} }
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => { Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
const previousMatch = getPreviousMatch(previousValue, nextSubProperty); const previousMatch = getPreviousMatch(
previousValue,
nextSubProperty,
options
);
if (!!!previousMatch && !!nextSubProperty) { if (!!!previousMatch && !!nextSubProperty) {
return subPropertiesDiff.push({ return subPropertiesDiff.push({
name: nextSubProperty, name: nextSubProperty,
@ -133,7 +142,8 @@ function getSubPropertiesDiff(previousValue, nextValue) {
if (isObject(nextSubValue)) { if (isObject(nextSubValue)) {
const data = getSubPropertiesDiff( const data = getSubPropertiesDiff(
previousMatch, previousMatch,
nextSubValue nextSubValue,
options
); );
if (data && data.length > 0) { if (data && data.length > 0) {
subDiff = data; subDiff = data;
@ -144,14 +154,14 @@ function getSubPropertiesDiff(previousValue, nextValue) {
name: nextSubProperty, name: nextSubProperty,
previousValue: previousMatch, previousValue: previousMatch,
currentValue: nextSubValue, currentValue: nextSubValue,
status: getValueStatus(previousMatch, nextSubValue), status: getValueStatus(previousMatch, nextSubValue, options),
...!!subDiff && { subDiff } ...!!subDiff && { subDiff }
}); });
} }
}); });
return subPropertiesDiff; return subPropertiesDiff;
} }
function getObjectDiff(prevData, nextData) { function getObjectDiff(prevData, nextData, options) {
if (!prevData && !nextData) { if (!prevData && !nextData) {
return { return {
type: "object", type: "object",
@ -179,7 +189,8 @@ function getObjectDiff(prevData, nextData) {
if (isObject(nextValue)) { if (isObject(nextValue)) {
const subPropertiesDiff = getSubPropertiesDiff( const subPropertiesDiff = getSubPropertiesDiff(
previousValue, previousValue,
nextValue nextValue,
options
); );
return diff.push({ return diff.push({
property: nextProperty, property: nextProperty,
@ -193,7 +204,7 @@ function getObjectDiff(prevData, nextData) {
property: nextProperty, property: nextProperty,
previousValue, previousValue,
currentValue: nextValue, currentValue: nextValue,
status: getValueStatus(previousValue, nextValue) status: getValueStatus(previousValue, nextValue, options)
}); });
}); });
const deletedProperties = getDeletedProperties(prevData, nextData); const deletedProperties = getDeletedProperties(prevData, nextData);

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "@donedeal0/superdiff", "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", "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", "main": "dist/index.js",
"module": "dist/index.js", "module": "dist/index.js",

1
src/model.ts

@ -9,6 +9,7 @@ export const STATUS: Record<string, DiffStatus> = {
export type DiffStatus = "added" | "equal" | "moved" | "deleted" | "updated"; export type DiffStatus = "added" | "equal" | "moved" | "deleted" | "updated";
export type ObjectData = Record<string, any> | undefined | null; export type ObjectData = Record<string, any> | undefined | null;
export type ListData = any; export type ListData = any;
export type Options = { discardArrayOrder?: boolean };
export type ListDiff = { export type ListDiff = {
type: "list"; type: "list";

36
src/object-diff.ts

@ -4,6 +4,7 @@ import {
DiffStatus, DiffStatus,
STATUS, STATUS,
Subproperties, Subproperties,
Options,
} from "./model"; } from "./model";
import { isObject, isEqual } from "./utils"; import { isObject, isEqual } from "./utils";
@ -60,19 +61,24 @@ function formatSingleObjectDiff(
function getPreviousMatch( function getPreviousMatch(
previousValue: any | undefined, previousValue: any | undefined,
nextSubProperty: any nextSubProperty: any,
options?: Options
): any | undefined { ): any | undefined {
if (!previousValue) { if (!previousValue) {
return undefined; return undefined;
} }
const previousMatch = Object.entries(previousValue).find(([subPreviousKey]) => const previousMatch = Object.entries(previousValue).find(([subPreviousKey]) =>
isEqual(subPreviousKey, nextSubProperty) isEqual(subPreviousKey, nextSubProperty, options)
); );
return previousMatch ? previousMatch[1] : undefined; return previousMatch ? previousMatch[1] : undefined;
} }
function getValueStatus(previousValue: any, nextValue: any): DiffStatus { function getValueStatus(
if (isEqual(previousValue, nextValue)) { previousValue: any,
nextValue: any,
options?: Options
): DiffStatus {
if (isEqual(previousValue, nextValue, options)) {
return STATUS.EQUAL; return STATUS.EQUAL;
} }
return STATUS.UPDATED; return STATUS.UPDATED;
@ -103,7 +109,8 @@ function getDeletedProperties(
function getSubPropertiesDiff( function getSubPropertiesDiff(
previousValue: Record<string, any> | undefined, previousValue: Record<string, any> | undefined,
nextValue: Record<string, any> nextValue: Record<string, any>,
options?: Options
): Subproperties[] { ): Subproperties[] {
const subPropertiesDiff: Subproperties[] = []; const subPropertiesDiff: Subproperties[] = [];
let subDiff: Subproperties[]; let subDiff: Subproperties[];
@ -122,7 +129,11 @@ function getSubPropertiesDiff(
}); });
} }
Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => { Object.entries(nextValue).forEach(([nextSubProperty, nextSubValue]) => {
const previousMatch = getPreviousMatch(previousValue, nextSubProperty); const previousMatch = getPreviousMatch(
previousValue,
nextSubProperty,
options
);
if (!!!previousMatch && !!nextSubProperty) { if (!!!previousMatch && !!nextSubProperty) {
return subPropertiesDiff.push({ return subPropertiesDiff.push({
name: nextSubProperty, name: nextSubProperty,
@ -134,7 +145,8 @@ function getSubPropertiesDiff(
if (isObject(nextSubValue)) { if (isObject(nextSubValue)) {
const data: Subproperties[] = getSubPropertiesDiff( const data: Subproperties[] = getSubPropertiesDiff(
previousMatch, previousMatch,
nextSubValue nextSubValue,
options
); );
if (data && data.length > 0) { if (data && data.length > 0) {
subDiff = data; subDiff = data;
@ -145,7 +157,7 @@ function getSubPropertiesDiff(
name: nextSubProperty, name: nextSubProperty,
previousValue: previousMatch, previousValue: previousMatch,
currentValue: nextSubValue, currentValue: nextSubValue,
status: getValueStatus(previousMatch, nextSubValue), status: getValueStatus(previousMatch, nextSubValue, options),
...(!!subDiff && { subDiff }), ...(!!subDiff && { subDiff }),
}); });
} }
@ -155,7 +167,8 @@ function getSubPropertiesDiff(
export function getObjectDiff( export function getObjectDiff(
prevData: ObjectData, prevData: ObjectData,
nextData: ObjectData nextData: ObjectData,
options?: Options
): ObjectDiff { ): ObjectDiff {
if (!prevData && !nextData) { if (!prevData && !nextData) {
return { return {
@ -184,7 +197,8 @@ export function getObjectDiff(
if (isObject(nextValue)) { if (isObject(nextValue)) {
const subPropertiesDiff: Subproperties[] = getSubPropertiesDiff( const subPropertiesDiff: Subproperties[] = getSubPropertiesDiff(
previousValue, previousValue,
nextValue nextValue,
options
); );
return diff.push({ return diff.push({
property: nextProperty, property: nextProperty,
@ -198,7 +212,7 @@ export function getObjectDiff(
property: nextProperty, property: nextProperty,
previousValue, previousValue,
currentValue: nextValue, currentValue: nextValue,
status: getValueStatus(previousValue, nextValue), status: getValueStatus(previousValue, nextValue, options),
}); });
}); });
const deletedProperties = getDeletedProperties(prevData, nextData); const deletedProperties = getDeletedProperties(prevData, nextData);

13
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 (typeof a !== typeof b) return false;
if (Array.isArray(a)) { if (Array.isArray(a)) {
if (a.length !== b.length) { if (a.length !== b.length) {
return false; 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])); return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i]));
} }
if (typeof a === "object") { if (typeof a === "object") {

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

Loading…
Cancel
Save