Browse Source

feat: simplify output, clean readme (#25)

BREAKING CHANGE: subPropertiesDiff has been removed from the getObjectDiff output. There is now a single recursive diff key for more simplicity. The types have also been improved.
pull/27/head v2.0.0
DoneDeal0 7 months ago committed by GitHub
parent
commit
c6a186efac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      .eslintcache
  2. 3
      .gitignore
  3. 141
      README.md
  4. 6
      eslint.config.mjs
  5. 2
      package.json
  6. 3
      src/index.ts
  7. 28
      src/list-diff.ts
  8. 93
      src/model.ts
  9. 35
      src/models/list.ts
  10. 41
      src/models/object.ts
  11. 3
      src/models/utils.ts
  12. 171
      src/object-diff.ts
  13. 18
      src/utils.ts
  14. 7
      test/list-diff.test.ts
  15. 58
      test/object-diff.test.ts

1
.eslintcache

@ -1 +0,0 @@ @@ -1 +0,0 @@
[{"/Users/antoine/Desktop/superdiff/eslint.config.mjs":"1","/Users/antoine/Desktop/superdiff/index.ts":"2","/Users/antoine/Desktop/superdiff/tsup.config.ts":"3","/Users/antoine/Desktop/superdiff/src/index.ts":"4","/Users/antoine/Desktop/superdiff/src/list-diff.ts":"5","/Users/antoine/Desktop/superdiff/src/model.ts":"6","/Users/antoine/Desktop/superdiff/src/object-diff.ts":"7","/Users/antoine/Desktop/superdiff/src/utils.ts":"8","/Users/antoine/Desktop/superdiff/test/list-diff.test.ts":"9","/Users/antoine/Desktop/superdiff/test/object-diff.test.ts":"10","/Users/antoine/Desktop/superdiff/test/utils.test.ts":"11"},{"size":426,"mtime":1727602294779,"results":"12","hashOfConfig":"13"},{"size":0,"mtime":1727601340027,"results":"14","hashOfConfig":"15"},{"size":222,"mtime":1727601767512,"results":"16","hashOfConfig":"15"},{"size":160,"mtime":1727601340028,"results":"17","hashOfConfig":"15"},{"size":4697,"mtime":1727601897826,"results":"18","hashOfConfig":"15"},{"size":1903,"mtime":1727601897832,"results":"19","hashOfConfig":"15"},{"size":9363,"mtime":1727602282176,"results":"20","hashOfConfig":"15"},{"size":1150,"mtime":1727601897855,"results":"21","hashOfConfig":"15"},{"size":18523,"mtime":1727601897889,"results":"22","hashOfConfig":"15"},{"size":23198,"mtime":1727601897918,"results":"23","hashOfConfig":"15"},{"size":3560,"mtime":1727601897925,"results":"24","hashOfConfig":"15"},{"filePath":"25","messages":"26","suppressedMessages":"27","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"13i3tau",{"filePath":"28","messages":"29","suppressedMessages":"30","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"a3qfx6",{"filePath":"31","messages":"32","suppressedMessages":"33","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"34","messages":"35","suppressedMessages":"36","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"37","messages":"38","suppressedMessages":"39","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"40","messages":"41","suppressedMessages":"42","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"43","messages":"44","suppressedMessages":"45","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"46","messages":"47","suppressedMessages":"48","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"49","messages":"50","suppressedMessages":"51","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"52","messages":"53","suppressedMessages":"54","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"55","messages":"56","suppressedMessages":"57","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"/Users/antoine/Desktop/superdiff/eslint.config.mjs",[],[],"/Users/antoine/Desktop/superdiff/index.ts",[],[],"/Users/antoine/Desktop/superdiff/tsup.config.ts",[],[],"/Users/antoine/Desktop/superdiff/src/index.ts",[],[],"/Users/antoine/Desktop/superdiff/src/list-diff.ts",[],[],"/Users/antoine/Desktop/superdiff/src/model.ts",[],[],"/Users/antoine/Desktop/superdiff/src/object-diff.ts",[],[],"/Users/antoine/Desktop/superdiff/src/utils.ts",[],[],"/Users/antoine/Desktop/superdiff/test/list-diff.test.ts",[],[],"/Users/antoine/Desktop/superdiff/test/object-diff.test.ts",[],[],"/Users/antoine/Desktop/superdiff/test/utils.test.ts",[],[]]

3
.gitignore vendored

@ -1,2 +1,3 @@ @@ -1,2 +1,3 @@
/node_modules
dist
dist
.eslintcache

141
README.md

@ -1,20 +1,27 @@ @@ -1,20 +1,27 @@
<img width="722" alt="superdiff-logo" src="https://user-images.githubusercontent.com/43271780/209532864-24d7449e-1185-4810-9423-be5df1fe877f.png">
# SUPERDIFF
This library compares two arrays or objects and returns a full diff of their differences.
[![CI](https://github.com/DoneDeal0/superdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/DoneDeal0/superdiff/actions/workflows/ci.yml)
[![CD](https://github.com/DoneDeal0/superdiff/actions/workflows/cd.yml/badge.svg)](https://github.com/DoneDeal0/superdiff/actions/workflows/cd.yml)
![NPM Downloads](https://img.shields.io/npm/dy/%40donedeal0%2Fsuperdiff?logo=npm)
![GitHub Tag](https://img.shields.io/github/v/tag/DoneDeal0/superdiff?label=latest%20release)
<hr/>
# WHAT IS IT?
This library compares two arrays or objects and returns a full diff of their differences.
<hr/>
## WHY YOU SHOULD USE THIS LIBRARY
All other existing solutions return a strange diff format that often requires additional parsing. They are also limited to object comparison. 👎
All other existing solutions return a strange diff format that often requires additional parsing. They are also limited to object comparison.
**Superdiff** gives you a complete diff for both array <u>and</u> objects in a very readable format. Last but not least, it's battle-tested and super fast. Import. Enjoy. 👍
<hr/>
## DONORS
I am grateful to the generous donors of **Superdiff**!
@ -27,111 +34,7 @@ I am grateful to the generous donors of **Superdiff**! @@ -27,111 +34,7 @@ I am grateful to the generous donors of **Superdiff**!
</div>
## DIFF FORMAT COMPARISON
Let's compare the diff format of **Superdiff** and **Deep-diff**, the most popular diff lib on npm:
input:
```diff
const objectA = {
id: 54,
user: {
name: "joe",
- member: true,
- hobbies: ["golf", "football"],
age: 66,
},
}
const objectB = {
id: 54,
user: {
name: "joe",
+ member: false,
+ hobbies: ["golf", "chess"],
age: 66,
},
}
```
**Deep-Diff** output:
```js
[
{
kind: "E",
path: ["user", "member"],
lhs: true,
rhs: false,
},
{
kind: "E",
path: ["user", "hobbies", 1],
lhs: "football",
rhs: "chess",
},
];
```
**SuperDiff** output:
```diff
{
type: "object",
+ status: "updated",
diff: [
{
property: "id",
previousValue: 54,
currentValue: 54,
status: "equal",
},
{
property: "user",
previousValue: {
name: "joe",
member: true,
hobbies: ["golf", "football"],
age: 66,
},
currentValue: {
name: "joe",
member: false,
hobbies: ["golf", "chess"],
age: 66,
},
+ status: "updated",
subPropertiesDiff: [
{
property: "name",
previousValue: "joe",
currentValue: "joe",
status: "equal",
},
+ {
+ property: "member",
+ previousValue: true,
+ currentValue: false,
+ status: "updated",
+ },
+ {
+ property: "hobbies",
+ previousValue: ["golf", "football"],
+ currentValue: ["golf", "chess"],
+ status: "updated",
+ },
{
property: "age",
previousValue: 66,
currentValue: 66,
status: "equal",
},
],
},
],
}
```
<hr/>
## FEATURES
@ -158,17 +61,17 @@ type ObjectDiff = { @@ -158,17 +61,17 @@ type ObjectDiff = {
status: "added" | "deleted" | "equal" | "updated";
diff: {
property: string;
previousValue: any;
currentValue: any;
previousValue: unknown;
currentValue: unknow;
status: "added" | "deleted" | "equal" | "updated";
// only appears if some subproperties have been added/deleted/updated
subPropertiesDiff?: {
diff?: {
property: string;
previousValue: any;
currentValue: any;
previousValue: unknown;
currentValue: unknown;
status: "added" | "deleted" | "equal" | "updated";
// subDiff is a recursive diff in case of nested subproperties
subDiff?: SubProperties[];
// recursive diff in case of subproperties
diff?: SubDiff[];
}[];
}[];
};
@ -217,7 +120,7 @@ type ListDiff = { @@ -217,7 +120,7 @@ type ListDiff = {
type: "list";
status: "added" | "deleted" | "equal" | "moved" | "updated";
diff: {
value: any;
value: unknown;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
@ -270,6 +173,8 @@ import { isObject } from "@donedeal0/superdiff"; @@ -270,6 +173,8 @@ import { isObject } from "@donedeal0/superdiff";
Tests whether a value is an object.
<hr/>
## EXAMPLES
### getListDiff()
@ -384,7 +289,7 @@ output @@ -384,7 +289,7 @@ output
age: 66,
},
+ status: "updated",
subPropertiesDiff: [
diff: [
{
property: "name",
previousValue: "joe",

6
eslint.config.mjs

@ -7,10 +7,4 @@ export default [ @@ -7,10 +7,4 @@ export default [
{ settings: { react: { version: "detect" } } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off"
},
},
];

2
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "@donedeal0/superdiff",
"version": "1.1.3",
"version": "2.0.0",
"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.mjs",

3
src/index.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
export { getObjectDiff } from "./object-diff";
export { getListDiff } from "./list-diff";
export { isEqual, isObject } from "./utils";
export * from "./model";
export * from "./models/list";
export * from "./models/object";

28
src/list-diff.ts

@ -1,17 +1,22 @@ @@ -1,17 +1,22 @@
import { LIST_STATUS, ListDiff, ListDiffStatus, ListOptions } from "./model";
import {
DEFAULT_LIST_DIFF_OPTIONS,
LIST_STATUS,
ListDiff,
ListDiffOptions,
} from "./models/list";
import { isEqual, isObject } from "./utils";
function getLeanDiff(
diff: ListDiff["diff"],
showOnly = [] as ListOptions["showOnly"],
showOnly = [] as ListDiffOptions["showOnly"],
): ListDiff["diff"] {
return diff.filter((value) => showOnly?.includes(value.status));
}
function formatSingleListDiff<T>(
listData: T[],
status: ListDiffStatus,
options: ListOptions = { showOnly: [] },
status: LIST_STATUS,
options: ListDiffOptions = { showOnly: [] },
): ListDiff {
const diff = listData.map((data, i) => ({
value: data,
@ -34,16 +39,16 @@ function formatSingleListDiff<T>( @@ -34,16 +39,16 @@ function formatSingleListDiff<T>(
};
}
function getListStatus(listDiff: ListDiff["diff"]): ListDiffStatus {
function getListStatus(listDiff: ListDiff["diff"]): LIST_STATUS {
return listDiff.some((value) => value.status !== LIST_STATUS.EQUAL)
? LIST_STATUS.UPDATED
: LIST_STATUS.EQUAL;
}
function isReferencedObject(
value: any,
referenceProperty: ListOptions["referenceProperty"],
): value is Record<string, any> {
value: unknown,
referenceProperty: ListDiffOptions["referenceProperty"],
): value is Record<string, unknown> {
if (isObject(value) && !!referenceProperty) {
return Object.hasOwn(value, referenceProperty);
}
@ -62,12 +67,7 @@ function isReferencedObject( @@ -62,12 +67,7 @@ function isReferencedObject(
export const getListDiff = <T>(
prevList: T[] | undefined | null,
nextList: T[] | undefined | null,
options: ListOptions = {
showOnly: [],
referenceProperty: undefined,
considerMoveAsUpdate: false,
ignoreArrayOrder: false,
},
options: ListDiffOptions = DEFAULT_LIST_DIFF_OPTIONS,
): ListDiff => {
if (!prevList && !nextList) {
return {

93
src/model.ts

@ -1,93 +0,0 @@ @@ -1,93 +0,0 @@
export const STATUS: Record<string, ObjectDiffStatus> = {
ADDED: "added",
EQUAL: "equal",
DELETED: "deleted",
UPDATED: "updated",
};
export const LIST_STATUS: Record<string, ListDiffStatus> = {
...STATUS,
MOVED: "moved",
};
export const GRANULARITY: Record<string, "basic" | "deep"> = {
BASIC: "basic",
DEEP: "deep",
};
export type ListDiffStatus =
| "added"
| "equal"
| "moved"
| "deleted"
| "updated";
export type ObjectDiffStatus = "added" | "equal" | "deleted" | "updated";
export type ObjectData = Record<string, any> | undefined | null;
export type ListData = any;
export type ObjectStatusTuple = readonly [
"added",
"equal",
"deleted",
"updated",
];
export type ListStatusTuple = readonly [
"added",
"equal",
"deleted",
"moved",
"updated",
];
export type isEqualOptions = {
ignoreArrayOrder?: boolean;
};
export type ObjectOptions = {
ignoreArrayOrder?: boolean;
showOnly?: {
statuses: Array<ObjectStatusTuple[number]>;
granularity?: (typeof GRANULARITY)[keyof typeof GRANULARITY];
};
};
export type ListOptions = {
showOnly?: Array<ListStatusTuple[number]>;
referenceProperty?: string;
considerMoveAsUpdate?: boolean;
ignoreArrayOrder?: boolean;
};
export type ListDiff = {
type: "list";
status: ListDiffStatus;
diff: {
value: ListData;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: ListDiffStatus;
}[];
};
export type SubProperties = {
property: string;
previousValue: any;
currentValue: any;
status: ObjectDiffStatus;
subPropertiesDiff?: SubProperties[];
};
export type ObjectDiff = {
type: "object";
status: ObjectDiffStatus;
diff: {
property: string;
previousValue: any;
currentValue: any;
status: ObjectDiffStatus;
subPropertiesDiff?: SubProperties[];
}[];
};
export type DataDiff = ListDiff | ObjectDiff;

35
src/models/list.ts

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
export enum LIST_STATUS {
ADDED = "added",
EQUAL = "equal",
DELETED = "deleted",
UPDATED = "updated",
MOVED = "moved",
}
export type ListData = unknown;
export type ListDiffOptions = {
showOnly?: `${LIST_STATUS}`[];
referenceProperty?: string;
considerMoveAsUpdate?: boolean;
ignoreArrayOrder?: boolean;
};
export const DEFAULT_LIST_DIFF_OPTIONS = {
showOnly: [],
referenceProperty: undefined,
considerMoveAsUpdate: false,
ignoreArrayOrder: false,
};
export type ListDiff = {
type: "list";
status: LIST_STATUS;
diff: {
value: ListData;
prevIndex: number | null;
newIndex: number | null;
indexDiff: number | null;
status: LIST_STATUS;
}[];
};

41
src/models/object.ts

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
export enum OBJECT_STATUS {
ADDED = "added",
EQUAL = "equal",
DELETED = "deleted",
UPDATED = "updated",
}
export enum GRANULARITY {
BASIC = "basic",
DEEP = "deep",
}
export type ObjectData = Record<string, unknown> | undefined | null;
export type ObjectDiffOptions = {
ignoreArrayOrder?: boolean;
showOnly?: {
statuses: `${OBJECT_STATUS}`[];
granularity?: `${GRANULARITY}`;
};
};
export const DEFAULT_OBJECT_DIFF_OPTIONS = {
ignoreArrayOrder: false,
showOnly: { statuses: [], granularity: GRANULARITY.BASIC },
};
/** recursive diff in case of subproperties */
export type Diff = {
property: string;
previousValue: unknown;
currentValue: unknown;
status: OBJECT_STATUS;
diff?: Diff[];
};
export type ObjectDiff = {
type: "object";
status: OBJECT_STATUS;
diff: Diff[];
};

3
src/models/utils.ts

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
export type isEqualOptions = {
ignoreArrayOrder?: boolean;
};

171
src/object-diff.ts

@ -1,42 +1,25 @@ @@ -1,42 +1,25 @@
import {
GRANULARITY,
STATUS,
OBJECT_STATUS,
ObjectData,
ObjectDiff,
ObjectDiffStatus,
ObjectOptions,
SubProperties,
} from "./model";
ObjectDiffOptions,
Diff,
DEFAULT_OBJECT_DIFF_OPTIONS,
} from "./models/object";
import { isEqual, isObject } from "./utils";
function getLeanDiff(
diff: ObjectDiff["diff"],
showOnly: ObjectOptions["showOnly"] = {
statuses: [],
granularity: GRANULARITY.BASIC,
},
showOnly: ObjectDiffOptions["showOnly"] = DEFAULT_OBJECT_DIFF_OPTIONS.showOnly,
): ObjectDiff["diff"] {
const { statuses, granularity } = showOnly;
return diff.reduce(
(acc, value) => {
if (granularity === GRANULARITY.DEEP && value.subPropertiesDiff) {
const cleanSubPropertiesDiff = getLeanDiff(
value.subPropertiesDiff,
showOnly,
);
if (cleanSubPropertiesDiff.length > 0) {
return [
...acc,
{ ...value, subPropertiesDiff: cleanSubPropertiesDiff },
];
}
}
// @ts-ignore
if (granularity === GRANULARITY.DEEP && value.subDiff) {
// @ts-ignore
const cleanSubDiff = getLeanDiff(value.subDiff, showOnly);
if (cleanSubDiff.length > 0) {
return [...acc, { ...value, subDiff: cleanSubDiff }];
if (granularity === GRANULARITY.DEEP && value.diff) {
const leanDiff = getLeanDiff(value.diff, showOnly);
if (leanDiff.length > 0) {
return [...acc, { ...value, diff: leanDiff }];
}
}
if (statuses.includes(value.status)) {
@ -48,51 +31,50 @@ function getLeanDiff( @@ -48,51 +31,50 @@ function getLeanDiff(
);
}
function getObjectStatus(diff: ObjectDiff["diff"]): ObjectDiffStatus {
return diff.some((property) => property.status !== STATUS.EQUAL)
? STATUS.UPDATED
: STATUS.EQUAL;
function getObjectStatus(diff: ObjectDiff["diff"]): OBJECT_STATUS {
return diff.some((property) => property.status !== OBJECT_STATUS.EQUAL)
? OBJECT_STATUS.UPDATED
: OBJECT_STATUS.EQUAL;
}
function formatSingleObjectDiff(
data: ObjectData,
status: ObjectDiffStatus,
options: ObjectOptions = {
ignoreArrayOrder: false,
showOnly: { statuses: [], granularity: GRANULARITY.BASIC },
},
status: OBJECT_STATUS,
options: ObjectDiffOptions = DEFAULT_OBJECT_DIFF_OPTIONS,
): ObjectDiff {
if (!data) {
return {
type: "object",
status: STATUS.EQUAL,
status: OBJECT_STATUS.EQUAL,
diff: [],
};
}
const diff: ObjectDiff["diff"] = [];
Object.entries(data).forEach(([property, value]) => {
if (isObject(value)) {
const subPropertiesDiff: SubProperties[] = [];
const subPropertiesDiff: Diff[] = [];
Object.entries(value).forEach(([subProperty, subValue]) => {
subPropertiesDiff.push({
property: subProperty,
previousValue: status === STATUS.ADDED ? undefined : subValue,
currentValue: status === STATUS.ADDED ? subValue : undefined,
previousValue: status === OBJECT_STATUS.ADDED ? undefined : subValue,
currentValue: status === OBJECT_STATUS.ADDED ? subValue : undefined,
status,
});
});
return diff.push({
property: property,
previousValue: status === STATUS.ADDED ? undefined : data[property],
currentValue: status === STATUS.ADDED ? value : undefined,
property,
previousValue:
status === OBJECT_STATUS.ADDED ? undefined : data[property],
currentValue: status === OBJECT_STATUS.ADDED ? value : undefined,
status,
subPropertiesDiff,
diff: subPropertiesDiff,
});
}
return diff.push({
property,
previousValue: status === STATUS.ADDED ? undefined : data[property],
currentValue: status === STATUS.ADDED ? value : undefined,
previousValue:
status === OBJECT_STATUS.ADDED ? undefined : data[property],
currentValue: status === OBJECT_STATUS.ADDED ? value : undefined,
status,
});
});
@ -111,10 +93,10 @@ function formatSingleObjectDiff( @@ -111,10 +93,10 @@ function formatSingleObjectDiff(
}
function getPreviousMatch(
previousValue: any | undefined,
nextSubProperty: any,
options?: ObjectOptions,
): any | undefined {
previousValue: unknown | undefined,
nextSubProperty: unknown,
options?: ObjectDiffOptions,
): unknown | undefined {
if (!previousValue) {
return undefined;
}
@ -125,28 +107,28 @@ function getPreviousMatch( @@ -125,28 +107,28 @@ function getPreviousMatch(
}
function getValueStatus(
previousValue: any,
nextValue: any,
options?: ObjectOptions,
): ObjectDiffStatus {
previousValue: unknown,
nextValue: unknown,
options?: ObjectDiffOptions,
): OBJECT_STATUS {
if (isEqual(previousValue, nextValue, options)) {
return STATUS.EQUAL;
return OBJECT_STATUS.EQUAL;
}
return STATUS.UPDATED;
return OBJECT_STATUS.UPDATED;
}
function getPropertyStatus(
subPropertiesDiff: SubProperties[],
): ObjectDiffStatus {
return subPropertiesDiff.some((property) => property.status !== STATUS.EQUAL)
? STATUS.UPDATED
: STATUS.EQUAL;
function getPropertyStatus(subPropertiesDiff: Diff[]): OBJECT_STATUS {
return subPropertiesDiff.some(
(property) => property.status !== OBJECT_STATUS.EQUAL,
)
? OBJECT_STATUS.UPDATED
: OBJECT_STATUS.EQUAL;
}
function getDeletedProperties(
previousValue: Record<string, any> | undefined,
nextValue: Record<string, any>,
): { property: string; value: any }[] | undefined {
previousValue: Record<string, unknown> | undefined,
nextValue: Record<string, unknown>,
): { property: string; value: unknown }[] | undefined {
if (!previousValue) return undefined;
const prevKeys = Object.keys(previousValue);
const nextKeys = Object.keys(nextValue);
@ -161,12 +143,12 @@ function getDeletedProperties( @@ -161,12 +143,12 @@ function getDeletedProperties(
}
function getSubPropertiesDiff(
previousValue: Record<string, any> | undefined,
nextValue: Record<string, any>,
options?: ObjectOptions,
): SubProperties[] {
const subPropertiesDiff: SubProperties[] = [];
let subDiff: SubProperties[];
previousValue: Record<string, unknown> | undefined,
nextValue: Record<string, unknown>,
options?: ObjectDiffOptions,
): Diff[] {
const subPropertiesDiff: Diff[] = [];
let subDiff: Diff[];
const deletedMainSubProperties = getDeletedProperties(
previousValue,
nextValue,
@ -177,7 +159,7 @@ function getSubPropertiesDiff( @@ -177,7 +159,7 @@ function getSubPropertiesDiff(
property: deletedProperty.property,
previousValue: deletedProperty.value,
currentValue: undefined,
status: STATUS.DELETED,
status: OBJECT_STATUS.DELETED,
});
});
}
@ -194,15 +176,15 @@ function getSubPropertiesDiff( @@ -194,15 +176,15 @@ function getSubPropertiesDiff(
currentValue: nextSubValue,
status:
!previousValue || !(nextSubProperty in previousValue)
? STATUS.ADDED
? OBJECT_STATUS.ADDED
: previousMatch === nextSubValue
? STATUS.EQUAL
: STATUS.UPDATED,
? OBJECT_STATUS.EQUAL
: OBJECT_STATUS.UPDATED,
});
}
if (isObject(nextSubValue)) {
const data: SubProperties[] = getSubPropertiesDiff(
previousMatch,
const data: Diff[] = getSubPropertiesDiff(
previousMatch as Record<string, unknown>,
nextSubValue,
options,
);
@ -216,7 +198,7 @@ function getSubPropertiesDiff( @@ -216,7 +198,7 @@ function getSubPropertiesDiff(
previousValue: previousMatch,
currentValue: nextSubValue,
status: getValueStatus(previousMatch, nextSubValue, options),
...(!!subDiff && { subDiff }),
...(!!subDiff && { diff: subDiff }),
});
}
});
@ -225,9 +207,9 @@ function getSubPropertiesDiff( @@ -225,9 +207,9 @@ function getSubPropertiesDiff(
/**
* Returns the diff between two objects
* @param {Record<string, any>} prevData - The original object.
* @param {Record<string, any>} nextData - The new object.
* * @param {ListOptions} options - Options to refine your output.
* @param {ObjectData} prevData - The original object.
* @param {ObjectData} nextData - The new object.
* * @param {ObjectOptions} options - Options to refine your output.
- `showOnly`: returns only the values whose status you are interested in. It takes two parameters: `statuses` and `granularity`
`statuses` are the status you want to see in the output (e.g. `["added", "equal"]`)
`granularity` can be either `basic` (to return only the main properties whose status matches your query) or `deep` (to return the main properties if some of their subproperties' status match your request. The subproperties are filtered accordingly).
@ -237,23 +219,20 @@ function getSubPropertiesDiff( @@ -237,23 +219,20 @@ function getSubPropertiesDiff(
export function getObjectDiff(
prevData: ObjectData,
nextData: ObjectData,
options: ObjectOptions = {
ignoreArrayOrder: false,
showOnly: { statuses: [], granularity: GRANULARITY.BASIC },
},
options: ObjectDiffOptions = DEFAULT_OBJECT_DIFF_OPTIONS,
): ObjectDiff {
if (!prevData && !nextData) {
return {
type: "object",
status: STATUS.EQUAL,
status: OBJECT_STATUS.EQUAL,
diff: [],
};
}
if (!prevData) {
return formatSingleObjectDiff(nextData, STATUS.ADDED, options);
return formatSingleObjectDiff(nextData, OBJECT_STATUS.ADDED, options);
}
if (!nextData) {
return formatSingleObjectDiff(prevData, STATUS.DELETED, options);
return formatSingleObjectDiff(prevData, OBJECT_STATUS.DELETED, options);
}
const diff: ObjectDiff["diff"] = [];
Object.entries(nextData).forEach(([nextProperty, nextValue]) => {
@ -264,15 +243,15 @@ export function getObjectDiff( @@ -264,15 +243,15 @@ export function getObjectDiff(
previousValue,
currentValue: nextValue,
status: !(nextProperty in prevData)
? STATUS.ADDED
? OBJECT_STATUS.ADDED
: previousValue === nextValue
? STATUS.EQUAL
: STATUS.UPDATED,
? OBJECT_STATUS.EQUAL
: OBJECT_STATUS.UPDATED,
});
}
if (isObject(nextValue)) {
const subPropertiesDiff: SubProperties[] = getSubPropertiesDiff(
previousValue,
const subPropertiesDiff: Diff[] = getSubPropertiesDiff(
previousValue as Record<string, unknown>,
nextValue,
options,
);
@ -282,7 +261,9 @@ export function getObjectDiff( @@ -282,7 +261,9 @@ export function getObjectDiff(
previousValue,
currentValue: nextValue,
status: subPropertyStatus,
...(subPropertyStatus !== STATUS.EQUAL && { subPropertiesDiff }),
...(subPropertyStatus !== OBJECT_STATUS.EQUAL && {
diff: subPropertiesDiff,
}),
});
}
return diff.push({
@ -299,7 +280,7 @@ export function getObjectDiff( @@ -299,7 +280,7 @@ export function getObjectDiff(
property: deletedProperty.property,
previousValue: deletedProperty.value,
currentValue: undefined,
status: STATUS.DELETED,
status: OBJECT_STATUS.DELETED,
});
});
}

18
src/utils.ts

@ -1,15 +1,15 @@ @@ -1,15 +1,15 @@
import { isEqualOptions } from "./model";
import { isEqualOptions } from "./models/utils";
/**
* Returns true if two data are equal
* @param {any} a - The original data.
* @param {any} b - The data to compare.
* @param {unknown} a - The original data.
* @param {unknown} b - The data to compare.
* @param {isEqualOptions} options - The options to compare the data.
* @returns boolean
*/
export function isEqual(
a: any,
b: any,
a: unknown,
b: unknown,
options: isEqualOptions = { ignoreArrayOrder: false },
): boolean {
if (typeof a !== typeof b) return false;
@ -19,7 +19,7 @@ export function isEqual( @@ -19,7 +19,7 @@ export function isEqual(
}
if (options.ignoreArrayOrder) {
return a.every((v) =>
b.some((nextV: any) => JSON.stringify(nextV) === JSON.stringify(v)),
b.some((nextV) => JSON.stringify(nextV) === JSON.stringify(v)),
);
}
return a.every((v, i) => JSON.stringify(v) === JSON.stringify(b[i]));
@ -32,9 +32,9 @@ export function isEqual( @@ -32,9 +32,9 @@ export function isEqual(
/**
* Returns true if the provided value is an object
* @param {any} value - The data to check.
* @returns value is Record<string, any>
* @param {unknown} value - The data to check.
* @returns value is Record<string, unknown>
*/
export function isObject(value: any): value is Record<string, any> {
export function isObject(value: unknown): value is Record<string, unknown> {
return !!value && typeof value === "object" && !Array.isArray(value);
}

7
test/list-diff.test.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import { getListDiff } from "../src/list-diff";
import { LIST_STATUS } from "../src/models/list";
describe("getListDiff", () => {
it("returns an empty diff if no lists are provided", () => {
@ -416,7 +417,7 @@ describe("getListDiff", () => { @@ -416,7 +417,7 @@ describe("getListDiff", () => {
false,
{ name: "joe", age: 88 },
],
{ showOnly: ["added", "deleted"] },
{ showOnly: [LIST_STATUS.ADDED, LIST_STATUS.DELETED] },
),
).toStrictEqual({
type: "list",
@ -461,7 +462,7 @@ describe("getListDiff", () => { @@ -461,7 +462,7 @@ describe("getListDiff", () => {
});
expect(
getListDiff(["mbappe", "mendes", "verratti", "ruiz"], null, {
showOnly: ["moved", "updated"],
showOnly: [LIST_STATUS.MOVED, LIST_STATUS.UPDATED],
}),
).toStrictEqual({
type: "list",
@ -472,7 +473,7 @@ describe("getListDiff", () => { @@ -472,7 +473,7 @@ describe("getListDiff", () => {
it("returns all values if their status match the required statuses", () => {
expect(
getListDiff(null, ["mbappe", "mendes", "verratti", "ruiz"], {
showOnly: ["added"],
showOnly: [LIST_STATUS.ADDED],
}),
).toStrictEqual({
type: "list",

58
test/object-diff.test.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { GRANULARITY, OBJECT_STATUS } from "../src/models/object";
import { getObjectDiff } from "../src/object-diff";
describe("getObjectDiff", () => {
@ -189,7 +190,7 @@ describe("getObjectDiff", () => { @@ -189,7 +190,7 @@ describe("getObjectDiff", () => {
nickname: "super joe",
},
status: "updated",
subPropertiesDiff: [
diff: [
{
property: "age",
previousValue: 66,
@ -294,7 +295,7 @@ describe("getObjectDiff", () => { @@ -294,7 +295,7 @@ describe("getObjectDiff", () => {
},
},
status: "updated",
subPropertiesDiff: [
diff: [
{
property: "name",
previousValue: "joe",
@ -318,7 +319,7 @@ describe("getObjectDiff", () => { @@ -318,7 +319,7 @@ describe("getObjectDiff", () => {
},
},
status: "updated",
subDiff: [
diff: [
{
property: "member",
previousValue: true,
@ -336,7 +337,7 @@ describe("getObjectDiff", () => { @@ -336,7 +337,7 @@ describe("getObjectDiff", () => {
golf: ["st andrews"],
},
status: "updated",
subDiff: [
diff: [
{
property: "rugby",
previousValue: ["france"],
@ -420,7 +421,7 @@ describe("getObjectDiff", () => { @@ -420,7 +421,7 @@ describe("getObjectDiff", () => {
nickname: "super joe",
},
status: "updated",
subPropertiesDiff: [
diff: [
{
property: "age",
previousValue: 66,
@ -485,7 +486,7 @@ describe("getObjectDiff", () => { @@ -485,7 +486,7 @@ describe("getObjectDiff", () => {
nickname: "super joe",
},
},
{ showOnly: { statuses: ["added"] } },
{ showOnly: { statuses: [OBJECT_STATUS.ADDED] } },
),
).toStrictEqual({
type: "object",
@ -523,7 +524,12 @@ describe("getObjectDiff", () => { @@ -523,7 +524,12 @@ describe("getObjectDiff", () => {
nickname: "super joe",
},
},
{ showOnly: { statuses: ["added", "deleted"], granularity: "deep" } },
{
showOnly: {
statuses: [OBJECT_STATUS.ADDED, OBJECT_STATUS.DELETED],
granularity: GRANULARITY.DEEP,
},
},
),
).toStrictEqual({
type: "object",
@ -550,7 +556,7 @@ describe("getObjectDiff", () => { @@ -550,7 +556,7 @@ describe("getObjectDiff", () => {
nickname: "super joe",
},
status: "updated",
subPropertiesDiff: [
diff: [
{
property: "age",
previousValue: 66,
@ -605,8 +611,8 @@ describe("getObjectDiff", () => { @@ -605,8 +611,8 @@ describe("getObjectDiff", () => {
},
{
showOnly: {
statuses: ["updated"],
granularity: "deep",
statuses: [OBJECT_STATUS.UPDATED],
granularity: GRANULARITY.DEEP,
},
},
),
@ -637,7 +643,7 @@ describe("getObjectDiff", () => { @@ -637,7 +643,7 @@ describe("getObjectDiff", () => {
},
},
status: "updated",
subPropertiesDiff: [
diff: [
{
property: "data",
previousValue: {
@ -655,7 +661,7 @@ describe("getObjectDiff", () => { @@ -655,7 +661,7 @@ describe("getObjectDiff", () => {
},
},
status: "updated",
subDiff: [
diff: [
{
property: "hobbies",
previousValue: {
@ -667,7 +673,7 @@ describe("getObjectDiff", () => { @@ -667,7 +673,7 @@ describe("getObjectDiff", () => {
golf: ["st andrews"],
},
status: "updated",
subDiff: [
diff: [
{
property: "football",
previousValue: ["psg"],
@ -713,8 +719,8 @@ describe("getObjectDiff", () => { @@ -713,8 +719,8 @@ describe("getObjectDiff", () => {
},
{
showOnly: {
statuses: ["added"],
granularity: "deep",
statuses: [OBJECT_STATUS.ADDED],
granularity: GRANULARITY.DEEP,
},
},
),
@ -744,7 +750,7 @@ describe("getObjectDiff", () => { @@ -744,7 +750,7 @@ describe("getObjectDiff", () => {
},
},
status: "updated",
subPropertiesDiff: [
diff: [
{
property: "data",
previousValue: {
@ -761,7 +767,7 @@ describe("getObjectDiff", () => { @@ -761,7 +767,7 @@ describe("getObjectDiff", () => {
},
},
status: "updated",
subDiff: [
diff: [
{
property: "hobbies",
previousValue: {
@ -772,7 +778,7 @@ describe("getObjectDiff", () => { @@ -772,7 +778,7 @@ describe("getObjectDiff", () => {
golf: ["st andrews"],
},
status: "updated",
subDiff: [
diff: [
{
property: "football",
previousValue: undefined,
@ -803,7 +809,12 @@ describe("getObjectDiff", () => { @@ -803,7 +809,12 @@ describe("getObjectDiff", () => {
age: 54,
hobbies: ["golf", "football"],
},
{ showOnly: { statuses: ["deleted"], granularity: "deep" } },
{
showOnly: {
statuses: [OBJECT_STATUS.DELETED],
granularity: GRANULARITY.DEEP,
},
},
),
).toStrictEqual({
type: "object",
@ -819,7 +830,12 @@ describe("getObjectDiff", () => { @@ -819,7 +830,12 @@ describe("getObjectDiff", () => {
hobbies: ["golf", "football"],
},
null,
{ showOnly: { statuses: ["added"], granularity: "deep" } },
{
showOnly: {
statuses: [OBJECT_STATUS.ADDED],
granularity: GRANULARITY.DEEP,
},
},
),
).toStrictEqual({
type: "object",
@ -831,7 +847,7 @@ describe("getObjectDiff", () => { @@ -831,7 +847,7 @@ describe("getObjectDiff", () => {
getObjectDiff(
{ name: "joe", age: 54, hobbies: ["golf", "football"] },
null,
{ showOnly: { statuses: ["deleted"] } },
{ showOnly: { statuses: [OBJECT_STATUS.DELETED] } },
),
).toStrictEqual({
type: "object",

Loading…
Cancel
Save