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; @@ -394,10 +394,35 @@ false;
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
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
Pull requests are welcome!

7
dist/index.d.ts vendored

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

35
dist/index.js vendored

@ -10,13 +10,18 @@ var STATUS = { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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);

35
dist/index.mjs vendored

@ -8,13 +8,18 @@ var STATUS = { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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) { @@ -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);

2
package.json

@ -1,6 +1,6 @@ @@ -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",

1
src/model.ts

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

36
src/object-diff.ts

@ -4,6 +4,7 @@ import { @@ -4,6 +4,7 @@ import {
DiffStatus,
STATUS,
Subproperties,
Options,
} from "./model";
import { isObject, isEqual } from "./utils";
@ -60,19 +61,24 @@ function formatSingleObjectDiff( @@ -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( @@ -103,7 +109,8 @@ function getDeletedProperties(
function getSubPropertiesDiff(
previousValue: Record<string, any> | undefined,
nextValue: Record<string, any>
nextValue: Record<string, any>,
options?: Options
): Subproperties[] {
const subPropertiesDiff: Subproperties[] = [];
let subDiff: Subproperties[];
@ -122,7 +129,11 @@ function getSubPropertiesDiff( @@ -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( @@ -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( @@ -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( @@ -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( @@ -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( @@ -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);

13
src/utils.ts

@ -1,9 +1,20 @@ @@ -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") {

98
test/object-diff.test.ts

@ -301,4 +301,102 @@ describe("getObjectDiff", () => { @@ -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