diff --git a/.eslintcache b/.eslintcache deleted file mode 100644 index 1ab57e8..0000000 --- a/.eslintcache +++ /dev/null @@ -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",[],[]] \ No newline at end of file diff --git a/.gitignore b/.gitignore index e507da3..19e6239 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules -dist \ No newline at end of file +dist +.eslintcache \ No newline at end of file diff --git a/README.md b/README.md index f902834..255351d 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,27 @@ superdiff-logo -# 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) +
+ +# WHAT IS IT? + +This library compares two arrays or objects and returns a full diff of their differences. + +
+ ## 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 and objects in a very readable format. Last but not least, it's battle-tested and super fast. Import. Enjoy. 👍 +
+ ## DONORS I am grateful to the generous donors of **Superdiff**! @@ -27,111 +34,7 @@ I am grateful to the generous donors of **Superdiff**! -## 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", - }, - ], - }, - ], - } -``` +
## FEATURES @@ -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 = { 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"; Tests whether a value is an object. +
+ ## EXAMPLES ### getListDiff() @@ -384,7 +289,7 @@ output age: 66, }, + status: "updated", - subPropertiesDiff: [ + diff: [ { property: "name", previousValue: "joe", diff --git a/eslint.config.mjs b/eslint.config.mjs index b8b52ab..769dd55 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -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" - }, - }, ]; diff --git a/package.json b/package.json index 5defdb4..d1be0f4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.ts b/src/index.ts index 59898d6..8eba074 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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"; diff --git a/src/list-diff.ts b/src/list-diff.ts index 143ec3f..0675938 100644 --- a/src/list-diff.ts +++ b/src/list-diff.ts @@ -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( 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( }; } -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 { + value: unknown, + referenceProperty: ListDiffOptions["referenceProperty"], +): value is Record { if (isObject(value) && !!referenceProperty) { return Object.hasOwn(value, referenceProperty); } @@ -62,12 +67,7 @@ function isReferencedObject( export const getListDiff = ( 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 { diff --git a/src/model.ts b/src/model.ts deleted file mode 100644 index 55abfbd..0000000 --- a/src/model.ts +++ /dev/null @@ -1,93 +0,0 @@ -export const STATUS: Record = { - ADDED: "added", - EQUAL: "equal", - DELETED: "deleted", - UPDATED: "updated", -}; - -export const LIST_STATUS: Record = { - ...STATUS, - MOVED: "moved", -}; - -export const GRANULARITY: Record = { - BASIC: "basic", - DEEP: "deep", -}; - -export type ListDiffStatus = - | "added" - | "equal" - | "moved" - | "deleted" - | "updated"; -export type ObjectDiffStatus = "added" | "equal" | "deleted" | "updated"; -export type ObjectData = Record | 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; - granularity?: (typeof GRANULARITY)[keyof typeof GRANULARITY]; - }; -}; - -export type ListOptions = { - showOnly?: Array; - 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; diff --git a/src/models/list.ts b/src/models/list.ts new file mode 100644 index 0000000..79fc9ba --- /dev/null +++ b/src/models/list.ts @@ -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; + }[]; +}; diff --git a/src/models/object.ts b/src/models/object.ts new file mode 100644 index 0000000..86fa19e --- /dev/null +++ b/src/models/object.ts @@ -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 | 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[]; +}; diff --git a/src/models/utils.ts b/src/models/utils.ts new file mode 100644 index 0000000..f8f0e86 --- /dev/null +++ b/src/models/utils.ts @@ -0,0 +1,3 @@ +export type isEqualOptions = { + ignoreArrayOrder?: boolean; +}; diff --git a/src/object-diff.ts b/src/object-diff.ts index 2c4cd63..1eb02ec 100644 --- a/src/object-diff.ts +++ b/src/object-diff.ts @@ -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( ); } -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( } 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( } 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 | undefined, - nextValue: Record, -): { property: string; value: any }[] | undefined { + previousValue: Record | undefined, + nextValue: Record, +): { 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( } function getSubPropertiesDiff( - previousValue: Record | undefined, - nextValue: Record, - options?: ObjectOptions, -): SubProperties[] { - const subPropertiesDiff: SubProperties[] = []; - let subDiff: SubProperties[]; + previousValue: Record | undefined, + nextValue: Record, + options?: ObjectDiffOptions, +): Diff[] { + const subPropertiesDiff: Diff[] = []; + let subDiff: Diff[]; const deletedMainSubProperties = getDeletedProperties( previousValue, nextValue, @@ -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( 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, nextSubValue, options, ); @@ -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( /** * Returns the diff between two objects - * @param {Record} prevData - The original object. - * @param {Record} 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( 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( 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, nextValue, options, ); @@ -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( property: deletedProperty.property, previousValue: deletedProperty.value, currentValue: undefined, - status: STATUS.DELETED, + status: OBJECT_STATUS.DELETED, }); }); } diff --git a/src/utils.ts b/src/utils.ts index e5945c4..b0662bd 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -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( } 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( /** * Returns true if the provided value is an object - * @param {any} value - The data to check. - * @returns value is Record + * @param {unknown} value - The data to check. + * @returns value is Record */ -export function isObject(value: any): value is Record { +export function isObject(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value); } diff --git a/test/list-diff.test.ts b/test/list-diff.test.ts index f526528..6826d9a 100644 --- a/test/list-diff.test.ts +++ b/test/list-diff.test.ts @@ -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", () => { false, { name: "joe", age: 88 }, ], - { showOnly: ["added", "deleted"] }, + { showOnly: [LIST_STATUS.ADDED, LIST_STATUS.DELETED] }, ), ).toStrictEqual({ type: "list", @@ -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", () => { 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", diff --git a/test/object-diff.test.ts b/test/object-diff.test.ts index 8673ffc..bb3c0c7 100644 --- a/test/object-diff.test.ts +++ b/test/object-diff.test.ts @@ -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", () => { nickname: "super joe", }, status: "updated", - subPropertiesDiff: [ + diff: [ { property: "age", previousValue: 66, @@ -294,7 +295,7 @@ describe("getObjectDiff", () => { }, }, status: "updated", - subPropertiesDiff: [ + diff: [ { property: "name", previousValue: "joe", @@ -318,7 +319,7 @@ describe("getObjectDiff", () => { }, }, status: "updated", - subDiff: [ + diff: [ { property: "member", previousValue: true, @@ -336,7 +337,7 @@ describe("getObjectDiff", () => { golf: ["st andrews"], }, status: "updated", - subDiff: [ + diff: [ { property: "rugby", previousValue: ["france"], @@ -420,7 +421,7 @@ describe("getObjectDiff", () => { nickname: "super joe", }, status: "updated", - subPropertiesDiff: [ + diff: [ { property: "age", previousValue: 66, @@ -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", () => { 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", () => { nickname: "super joe", }, status: "updated", - subPropertiesDiff: [ + diff: [ { property: "age", previousValue: 66, @@ -605,8 +611,8 @@ describe("getObjectDiff", () => { }, { showOnly: { - statuses: ["updated"], - granularity: "deep", + statuses: [OBJECT_STATUS.UPDATED], + granularity: GRANULARITY.DEEP, }, }, ), @@ -637,7 +643,7 @@ describe("getObjectDiff", () => { }, }, status: "updated", - subPropertiesDiff: [ + diff: [ { property: "data", previousValue: { @@ -655,7 +661,7 @@ describe("getObjectDiff", () => { }, }, status: "updated", - subDiff: [ + diff: [ { property: "hobbies", previousValue: { @@ -667,7 +673,7 @@ describe("getObjectDiff", () => { golf: ["st andrews"], }, status: "updated", - subDiff: [ + diff: [ { property: "football", previousValue: ["psg"], @@ -713,8 +719,8 @@ describe("getObjectDiff", () => { }, { showOnly: { - statuses: ["added"], - granularity: "deep", + statuses: [OBJECT_STATUS.ADDED], + granularity: GRANULARITY.DEEP, }, }, ), @@ -744,7 +750,7 @@ describe("getObjectDiff", () => { }, }, status: "updated", - subPropertiesDiff: [ + diff: [ { property: "data", previousValue: { @@ -761,7 +767,7 @@ describe("getObjectDiff", () => { }, }, status: "updated", - subDiff: [ + diff: [ { property: "hobbies", previousValue: { @@ -772,7 +778,7 @@ describe("getObjectDiff", () => { golf: ["st andrews"], }, status: "updated", - subDiff: [ + diff: [ { property: "football", previousValue: undefined, @@ -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", () => { 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", () => { getObjectDiff( { name: "joe", age: 54, hobbies: ["golf", "football"] }, null, - { showOnly: { statuses: ["deleted"] } }, + { showOnly: { statuses: [OBJECT_STATUS.DELETED] } }, ), ).toStrictEqual({ type: "object",