Browse Source

feat: add considerMoveAsUpdate option

pull/18/head
DoneDeal0 1 year ago
parent
commit
a8a3849f79
  1. 9
      dist/index.d.mts
  2. 9
      dist/index.d.ts
  3. 4
      dist/index.js
  4. 4
      dist/index.mjs
  5. 4
      package-lock.json
  6. 2
      package.json
  7. 13
      src/list-diff.ts
  8. 1
      src/model.ts
  9. 5
      src/object-diff.ts
  10. 110
      test/list-diff.test.ts

9
dist/index.d.mts vendored

@ -29,6 +29,7 @@ type ObjectOptions = {
type ListOptions = { type ListOptions = {
showOnly?: Array<ListStatusTuple[number]>; showOnly?: Array<ListStatusTuple[number]>;
referenceProperty?: string; referenceProperty?: string;
considerMoveAsUpdate?: boolean;
}; };
type ListDiff = { type ListDiff = {
type: "list"; type: "list";
@ -64,6 +65,11 @@ type ObjectDiff = {
* Returns the diff between two objects * Returns the diff between two objects
* @param {Record<string, any>} prevData - The original object. * @param {Record<string, any>} prevData - The original object.
* @param {Record<string, any>} nextData - The new object. * @param {Record<string, any>} nextData - The new object.
* * @param {ListOptions} 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).
- `ignoreArrayOrder` if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order.
* @returns ObjectDiff * @returns ObjectDiff
*/ */
declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, options?: ObjectOptions): ObjectDiff; declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, options?: ObjectOptions): ObjectDiff;
@ -72,6 +78,9 @@ declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, optio
* Returns the diff between two arrays * Returns the diff between two arrays
* @param {Array<T>} prevList - The original array. * @param {Array<T>} prevList - The original array.
* @param {Array<T>} nextList - The new array. * @param {Array<T>} nextList - The new array.
* @param {ListOptions} options - Options to refine your output.
- `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`).
- `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes.
* @returns ListDiff * @returns ListDiff
*/ */
declare const getListDiff: <T>(prevList: T[] | null | undefined, nextList: T[] | null | undefined, options?: ListOptions) => ListDiff; declare const getListDiff: <T>(prevList: T[] | null | undefined, nextList: T[] | null | undefined, options?: ListOptions) => ListDiff;

9
dist/index.d.ts vendored

@ -29,6 +29,7 @@ type ObjectOptions = {
type ListOptions = { type ListOptions = {
showOnly?: Array<ListStatusTuple[number]>; showOnly?: Array<ListStatusTuple[number]>;
referenceProperty?: string; referenceProperty?: string;
considerMoveAsUpdate?: boolean;
}; };
type ListDiff = { type ListDiff = {
type: "list"; type: "list";
@ -64,6 +65,11 @@ type ObjectDiff = {
* Returns the diff between two objects * Returns the diff between two objects
* @param {Record<string, any>} prevData - The original object. * @param {Record<string, any>} prevData - The original object.
* @param {Record<string, any>} nextData - The new object. * @param {Record<string, any>} nextData - The new object.
* * @param {ListOptions} 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).
- `ignoreArrayOrder` if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order.
* @returns ObjectDiff * @returns ObjectDiff
*/ */
declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, options?: ObjectOptions): ObjectDiff; declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, options?: ObjectOptions): ObjectDiff;
@ -72,6 +78,9 @@ declare function getObjectDiff(prevData: ObjectData, nextData: ObjectData, optio
* Returns the diff between two arrays * Returns the diff between two arrays
* @param {Array<T>} prevList - The original array. * @param {Array<T>} prevList - The original array.
* @param {Array<T>} nextList - The new array. * @param {Array<T>} nextList - The new array.
* @param {ListOptions} options - Options to refine your output.
- `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`).
- `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes.
* @returns ListDiff * @returns ListDiff
*/ */
declare const getListDiff: <T>(prevList: T[] | null | undefined, nextList: T[] | null | undefined, options?: ListOptions) => ListDiff; declare const getListDiff: <T>(prevList: T[] | null | undefined, nextList: T[] | null | undefined, options?: ListOptions) => ListDiff;

4
dist/index.js vendored

@ -1,8 +1,8 @@
'use strict'; 'use strict';
var u={ADDED:"added",EQUAL:"equal",DELETED:"deleted",UPDATED:"updated"},d={...u,MOVED:"moved"},y={BASIC:"basic",DEEP:"deep"};function a(e,t,f={ignoreArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)?e.length!==t.length?!1:f.ignoreArrayOrder?e.every(i=>t.some(n=>JSON.stringify(n)===JSON.stringify(i))):e.every((i,n)=>JSON.stringify(i)===JSON.stringify(t[n])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function p(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function O(e,t={statuses:[],granularity:y.BASIC}){let{statuses:f,granularity:i}=t;return e.reduce((n,r)=>{if(i===y.DEEP&&r.subPropertiesDiff){let s=O(r.subPropertiesDiff,t);if(s.length>0)return [...n,{...r,subPropertiesDiff:s}]}if(i===y.DEEP&&r.subDiff){let s=O(r.subDiff,t);if(s.length>0)return [...n,{...r,subDiff:s}]}return f.includes(r.status)?[...n,r]:n},[])}function E(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function A(e,t,f={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e)return {type:"object",status:u.EQUAL,diff:[]};let i=[];return Object.entries(e).forEach(([n,r])=>{if(p(r)){let s=[];return Object.entries(r).forEach(([o,c])=>{s.push({property:o,previousValue:t===u.ADDED?void 0:c,currentValue:t===u.ADDED?c:void 0,status:t});}),i.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t,subPropertiesDiff:s})}return i.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t})}),f.showOnly&&f.showOnly.statuses.length>0?{type:"object",status:t,diff:O(i,f.showOnly)}:{type:"object",status:t,diff:i}}function P(e,t,f){if(!e)return;let i=Object.entries(e).find(([n])=>a(n,t,f));return i?i[1]:void 0}function j(e,t,f){return a(e,t,f)?u.EQUAL:u.UPDATED}function U(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function g(e,t){if(!e)return;let f=Object.keys(e),i=Object.keys(t),n=f.filter(r=>!i.includes(r));if(n.length>0)return n.map(r=>({property:r,value:e[r]}))}function S(e,t,f){let i=[],n,r=g(e,t);return r&&r.forEach(s=>{i.push({property:s.property,previousValue:s.value,currentValue:void 0,status:u.DELETED});}),Object.entries(t).forEach(([s,o])=>{let c=P(e,s,f);if(!c)return i.push({property:s,previousValue:c,currentValue:o,status:!e||!(s in e)?u.ADDED:c===o?u.EQUAL:u.UPDATED});if(p(o)){let D=S(c,o,f);D&&D.length>0&&(n=D);}c&&i.push({property:s,previousValue:c,currentValue:o,status:j(c,o,f),...!!n&&{subDiff:n}});}),i}function m(e,t,f={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e&&!t)return {type:"object",status:u.EQUAL,diff:[]};if(!e)return A(t,u.ADDED,f);if(!t)return A(e,u.DELETED,f);let i=[];Object.entries(t).forEach(([r,s])=>{let o=e[r];if(!o)return i.push({property:r,previousValue:o,currentValue:s,status:r in e?o===s?u.EQUAL:u.UPDATED:u.ADDED});if(p(s)){let c=S(o,s,f),D=U(c);return i.push({property:r,previousValue:o,currentValue:s,status:D,...D!==u.EQUAL&&{subPropertiesDiff:c}})}return i.push({property:r,previousValue:o,currentValue:s,status:j(o,s,f)})});let n=g(e,t);return n&&n.forEach(r=>{i.push({property:r.property,previousValue:r.value,currentValue:void 0,status:u.DELETED});}),f.showOnly&&f.showOnly.statuses.length>0?{type:"object",status:E(i),diff:O(i,f.showOnly)}:{type:"object",status:E(i),diff:i}}function w(e,t=[]){return e.filter(f=>t?.includes(f.status))}function h(e,t,f={showOnly:[]}){let i=e.map((n,r)=>({value:n,prevIndex:t===d.ADDED?null:r,newIndex:t===d.ADDED?r:null,indexDiff:null,status:t}));return f.showOnly&&f.showOnly.length>0?{type:"list",status:t,diff:i.filter(n=>f.showOnly?.includes(n.status))}:{type:"list",status:t,diff:i}}function L(e){return e.some(t=>t.status!==d.EQUAL)?d.UPDATED:d.EQUAL}function T(e,t){return p(e)&&t?Object.hasOwn(e,t):!1}var I=(e,t,f={showOnly:[],referenceProperty:void 0})=>{if(!e&&!t)return {type:"list",status:d.EQUAL,diff:[]};if(!e)return h(t,d.ADDED,f);if(!t)return h(e,d.DELETED,f);let i=[],n=[];return t.forEach((r,s)=>{let o=e.findIndex((D,b)=>T(D,f.referenceProperty)?p(r)?a(D[f.referenceProperty],r[f.referenceProperty])&&!n.includes(b):!1:a(D,r)&&!n.includes(b));o>-1&&n.push(o);let c=o===-1?null:s-o;if(c===0){let D=d.EQUAL;return T(r,f.referenceProperty)&&(a(e[o],r)||(D=d.UPDATED)),i.push({value:r,prevIndex:o,newIndex:s,indexDiff:c,status:D})}return o===-1?i.push({value:r,prevIndex:null,newIndex:s,indexDiff:c,status:d.ADDED}):i.push({value:r,prevIndex:o,newIndex:s,indexDiff:c,status:d.MOVED})}),e.forEach((r,s)=>{if(!n.includes(s))return i.splice(s,0,{value:r,prevIndex:s,newIndex:null,indexDiff:null,status:d.DELETED})}),f.showOnly&&f?.showOnly?.length>0?{type:"list",status:L(i),diff:w(i,f.showOnly)}:{type:"list",status:L(i),diff:i}}; var u={ADDED:"added",EQUAL:"equal",DELETED:"deleted",UPDATED:"updated"},d={...u,MOVED:"moved"},y={BASIC:"basic",DEEP:"deep"};function D(e,t,i={ignoreArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)?e.length!==t.length?!1:i.ignoreArrayOrder?e.every(f=>t.some(n=>JSON.stringify(n)===JSON.stringify(f))):e.every((f,n)=>JSON.stringify(f)===JSON.stringify(t[n])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function p(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function O(e,t={statuses:[],granularity:y.BASIC}){let{statuses:i,granularity:f}=t;return e.reduce((n,r)=>{if(f===y.DEEP&&r.subPropertiesDiff){let s=O(r.subPropertiesDiff,t);if(s.length>0)return [...n,{...r,subPropertiesDiff:s}]}if(f===y.DEEP&&r.subDiff){let s=O(r.subDiff,t);if(s.length>0)return [...n,{...r,subDiff:s}]}return i.includes(r.status)?[...n,r]:n},[])}function E(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function A(e,t,i={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e)return {type:"object",status:u.EQUAL,diff:[]};let f=[];return Object.entries(e).forEach(([n,r])=>{if(p(r)){let s=[];return Object.entries(r).forEach(([o,c])=>{s.push({property:o,previousValue:t===u.ADDED?void 0:c,currentValue:t===u.ADDED?c:void 0,status:t});}),f.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t,subPropertiesDiff:s})}return f.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t})}),i.showOnly&&i.showOnly.statuses.length>0?{type:"object",status:t,diff:O(f,i.showOnly)}:{type:"object",status:t,diff:f}}function P(e,t,i){if(!e)return;let f=Object.entries(e).find(([n])=>D(n,t,i));return f?f[1]:void 0}function j(e,t,i){return D(e,t,i)?u.EQUAL:u.UPDATED}function U(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function g(e,t){if(!e)return;let i=Object.keys(e),f=Object.keys(t),n=i.filter(r=>!f.includes(r));if(n.length>0)return n.map(r=>({property:r,value:e[r]}))}function S(e,t,i){let f=[],n,r=g(e,t);return r&&r.forEach(s=>{f.push({property:s.property,previousValue:s.value,currentValue:void 0,status:u.DELETED});}),Object.entries(t).forEach(([s,o])=>{let c=P(e,s,i);if(!c)return f.push({property:s,previousValue:c,currentValue:o,status:!e||!(s in e)?u.ADDED:c===o?u.EQUAL:u.UPDATED});if(p(o)){let a=S(c,o,i);a&&a.length>0&&(n=a);}c&&f.push({property:s,previousValue:c,currentValue:o,status:j(c,o,i),...!!n&&{subDiff:n}});}),f}function m(e,t,i={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e&&!t)return {type:"object",status:u.EQUAL,diff:[]};if(!e)return A(t,u.ADDED,i);if(!t)return A(e,u.DELETED,i);let f=[];Object.entries(t).forEach(([r,s])=>{let o=e[r];if(!o)return f.push({property:r,previousValue:o,currentValue:s,status:r in e?o===s?u.EQUAL:u.UPDATED:u.ADDED});if(p(s)){let c=S(o,s,i),a=U(c);return f.push({property:r,previousValue:o,currentValue:s,status:a,...a!==u.EQUAL&&{subPropertiesDiff:c}})}return f.push({property:r,previousValue:o,currentValue:s,status:j(o,s,i)})});let n=g(e,t);return n&&n.forEach(r=>{f.push({property:r.property,previousValue:r.value,currentValue:void 0,status:u.DELETED});}),i.showOnly&&i.showOnly.statuses.length>0?{type:"object",status:E(f),diff:O(f,i.showOnly)}:{type:"object",status:E(f),diff:f}}function w(e,t=[]){return e.filter(i=>t?.includes(i.status))}function h(e,t,i={showOnly:[]}){let f=e.map((n,r)=>({value:n,prevIndex:t===d.ADDED?null:r,newIndex:t===d.ADDED?r:null,indexDiff:null,status:t}));return i.showOnly&&i.showOnly.length>0?{type:"list",status:t,diff:f.filter(n=>i.showOnly?.includes(n.status))}:{type:"list",status:t,diff:f}}function L(e){return e.some(t=>t.status!==d.EQUAL)?d.UPDATED:d.EQUAL}function T(e,t){return p(e)&&t?Object.hasOwn(e,t):!1}var I=(e,t,i={showOnly:[],referenceProperty:void 0,considerMoveAsUpdate:!1})=>{if(!e&&!t)return {type:"list",status:d.EQUAL,diff:[]};if(!e)return h(t,d.ADDED,i);if(!t)return h(e,d.DELETED,i);let f=[],n=[];return t.forEach((r,s)=>{let o=e.findIndex((a,b)=>T(a,i.referenceProperty)?p(r)?D(a[i.referenceProperty],r[i.referenceProperty])&&!n.includes(b):!1:D(a,r)&&!n.includes(b));o>-1&&n.push(o);let c=o===-1?null:s-o;if(c===0){let a=d.EQUAL;return T(r,i.referenceProperty)&&(D(e[o],r)||(a=d.UPDATED)),f.push({value:r,prevIndex:o,newIndex:s,indexDiff:c,status:a})}return o===-1?f.push({value:r,prevIndex:null,newIndex:s,indexDiff:c,status:d.ADDED}):f.push({value:r,prevIndex:o,newIndex:s,indexDiff:c,status:i.considerMoveAsUpdate?d.UPDATED:d.MOVED})}),e.forEach((r,s)=>{if(!n.includes(s))return f.splice(s,0,{value:r,prevIndex:s,newIndex:null,indexDiff:null,status:d.DELETED})}),i.showOnly&&i?.showOnly?.length>0?{type:"list",status:L(f),diff:w(f,i.showOnly)}:{type:"list",status:L(f),diff:f}};
exports.getListDiff = I; exports.getListDiff = I;
exports.getObjectDiff = m; exports.getObjectDiff = m;
exports.isEqual = a; exports.isEqual = D;
exports.isObject = p; exports.isObject = p;

4
dist/index.mjs vendored

@ -1,3 +1,3 @@
var u={ADDED:"added",EQUAL:"equal",DELETED:"deleted",UPDATED:"updated"},d={...u,MOVED:"moved"},y={BASIC:"basic",DEEP:"deep"};function a(e,t,f={ignoreArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)?e.length!==t.length?!1:f.ignoreArrayOrder?e.every(i=>t.some(n=>JSON.stringify(n)===JSON.stringify(i))):e.every((i,n)=>JSON.stringify(i)===JSON.stringify(t[n])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function p(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function b(e,t={statuses:[],granularity:y.BASIC}){let{statuses:f,granularity:i}=t;return e.reduce((n,r)=>{if(i===y.DEEP&&r.subPropertiesDiff){let s=b(r.subPropertiesDiff,t);if(s.length>0)return [...n,{...r,subPropertiesDiff:s}]}if(i===y.DEEP&&r.subDiff){let s=b(r.subDiff,t);if(s.length>0)return [...n,{...r,subDiff:s}]}return f.includes(r.status)?[...n,r]:n},[])}function A(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function j(e,t,f={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e)return {type:"object",status:u.EQUAL,diff:[]};let i=[];return Object.entries(e).forEach(([n,r])=>{if(p(r)){let s=[];return Object.entries(r).forEach(([o,c])=>{s.push({property:o,previousValue:t===u.ADDED?void 0:c,currentValue:t===u.ADDED?c:void 0,status:t});}),i.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t,subPropertiesDiff:s})}return i.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t})}),f.showOnly&&f.showOnly.statuses.length>0?{type:"object",status:t,diff:b(i,f.showOnly)}:{type:"object",status:t,diff:i}}function U(e,t,f){if(!e)return;let i=Object.entries(e).find(([n])=>a(n,t,f));return i?i[1]:void 0}function g(e,t,f){return a(e,t,f)?u.EQUAL:u.UPDATED}function m(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function S(e,t){if(!e)return;let f=Object.keys(e),i=Object.keys(t),n=f.filter(r=>!i.includes(r));if(n.length>0)return n.map(r=>({property:r,value:e[r]}))}function h(e,t,f){let i=[],n,r=S(e,t);return r&&r.forEach(s=>{i.push({property:s.property,previousValue:s.value,currentValue:void 0,status:u.DELETED});}),Object.entries(t).forEach(([s,o])=>{let c=U(e,s,f);if(!c)return i.push({property:s,previousValue:c,currentValue:o,status:!e||!(s in e)?u.ADDED:c===o?u.EQUAL:u.UPDATED});if(p(o)){let D=h(c,o,f);D&&D.length>0&&(n=D);}c&&i.push({property:s,previousValue:c,currentValue:o,status:g(c,o,f),...!!n&&{subDiff:n}});}),i}function w(e,t,f={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e&&!t)return {type:"object",status:u.EQUAL,diff:[]};if(!e)return j(t,u.ADDED,f);if(!t)return j(e,u.DELETED,f);let i=[];Object.entries(t).forEach(([r,s])=>{let o=e[r];if(!o)return i.push({property:r,previousValue:o,currentValue:s,status:r in e?o===s?u.EQUAL:u.UPDATED:u.ADDED});if(p(s)){let c=h(o,s,f),D=m(c);return i.push({property:r,previousValue:o,currentValue:s,status:D,...D!==u.EQUAL&&{subPropertiesDiff:c}})}return i.push({property:r,previousValue:o,currentValue:s,status:g(o,s,f)})});let n=S(e,t);return n&&n.forEach(r=>{i.push({property:r.property,previousValue:r.value,currentValue:void 0,status:u.DELETED});}),f.showOnly&&f.showOnly.statuses.length>0?{type:"object",status:A(i),diff:b(i,f.showOnly)}:{type:"object",status:A(i),diff:i}}function I(e,t=[]){return e.filter(f=>t?.includes(f.status))}function L(e,t,f={showOnly:[]}){let i=e.map((n,r)=>({value:n,prevIndex:t===d.ADDED?null:r,newIndex:t===d.ADDED?r:null,indexDiff:null,status:t}));return f.showOnly&&f.showOnly.length>0?{type:"list",status:t,diff:i.filter(n=>f.showOnly?.includes(n.status))}:{type:"list",status:t,diff:i}}function T(e){return e.some(t=>t.status!==d.EQUAL)?d.UPDATED:d.EQUAL}function P(e,t){return p(e)&&t?Object.hasOwn(e,t):!1}var R=(e,t,f={showOnly:[],referenceProperty:void 0})=>{if(!e&&!t)return {type:"list",status:d.EQUAL,diff:[]};if(!e)return L(t,d.ADDED,f);if(!t)return L(e,d.DELETED,f);let i=[],n=[];return t.forEach((r,s)=>{let o=e.findIndex((D,E)=>P(D,f.referenceProperty)?p(r)?a(D[f.referenceProperty],r[f.referenceProperty])&&!n.includes(E):!1:a(D,r)&&!n.includes(E));o>-1&&n.push(o);let c=o===-1?null:s-o;if(c===0){let D=d.EQUAL;return P(r,f.referenceProperty)&&(a(e[o],r)||(D=d.UPDATED)),i.push({value:r,prevIndex:o,newIndex:s,indexDiff:c,status:D})}return o===-1?i.push({value:r,prevIndex:null,newIndex:s,indexDiff:c,status:d.ADDED}):i.push({value:r,prevIndex:o,newIndex:s,indexDiff:c,status:d.MOVED})}),e.forEach((r,s)=>{if(!n.includes(s))return i.splice(s,0,{value:r,prevIndex:s,newIndex:null,indexDiff:null,status:d.DELETED})}),f.showOnly&&f?.showOnly?.length>0?{type:"list",status:T(i),diff:I(i,f.showOnly)}:{type:"list",status:T(i),diff:i}}; var u={ADDED:"added",EQUAL:"equal",DELETED:"deleted",UPDATED:"updated"},d={...u,MOVED:"moved"},y={BASIC:"basic",DEEP:"deep"};function D(e,t,i={ignoreArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)?e.length!==t.length?!1:i.ignoreArrayOrder?e.every(f=>t.some(n=>JSON.stringify(n)===JSON.stringify(f))):e.every((f,n)=>JSON.stringify(f)===JSON.stringify(t[n])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function p(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function b(e,t={statuses:[],granularity:y.BASIC}){let{statuses:i,granularity:f}=t;return e.reduce((n,r)=>{if(f===y.DEEP&&r.subPropertiesDiff){let s=b(r.subPropertiesDiff,t);if(s.length>0)return [...n,{...r,subPropertiesDiff:s}]}if(f===y.DEEP&&r.subDiff){let s=b(r.subDiff,t);if(s.length>0)return [...n,{...r,subDiff:s}]}return i.includes(r.status)?[...n,r]:n},[])}function A(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function j(e,t,i={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e)return {type:"object",status:u.EQUAL,diff:[]};let f=[];return Object.entries(e).forEach(([n,r])=>{if(p(r)){let s=[];return Object.entries(r).forEach(([o,c])=>{s.push({property:o,previousValue:t===u.ADDED?void 0:c,currentValue:t===u.ADDED?c:void 0,status:t});}),f.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t,subPropertiesDiff:s})}return f.push({property:n,previousValue:t===u.ADDED?void 0:e[n],currentValue:t===u.ADDED?r:void 0,status:t})}),i.showOnly&&i.showOnly.statuses.length>0?{type:"object",status:t,diff:b(f,i.showOnly)}:{type:"object",status:t,diff:f}}function U(e,t,i){if(!e)return;let f=Object.entries(e).find(([n])=>D(n,t,i));return f?f[1]:void 0}function g(e,t,i){return D(e,t,i)?u.EQUAL:u.UPDATED}function m(e){return e.some(t=>t.status!==u.EQUAL)?u.UPDATED:u.EQUAL}function S(e,t){if(!e)return;let i=Object.keys(e),f=Object.keys(t),n=i.filter(r=>!f.includes(r));if(n.length>0)return n.map(r=>({property:r,value:e[r]}))}function h(e,t,i){let f=[],n,r=S(e,t);return r&&r.forEach(s=>{f.push({property:s.property,previousValue:s.value,currentValue:void 0,status:u.DELETED});}),Object.entries(t).forEach(([s,o])=>{let c=U(e,s,i);if(!c)return f.push({property:s,previousValue:c,currentValue:o,status:!e||!(s in e)?u.ADDED:c===o?u.EQUAL:u.UPDATED});if(p(o)){let a=h(c,o,i);a&&a.length>0&&(n=a);}c&&f.push({property:s,previousValue:c,currentValue:o,status:g(c,o,i),...!!n&&{subDiff:n}});}),f}function w(e,t,i={ignoreArrayOrder:!1,showOnly:{statuses:[],granularity:y.BASIC}}){if(!e&&!t)return {type:"object",status:u.EQUAL,diff:[]};if(!e)return j(t,u.ADDED,i);if(!t)return j(e,u.DELETED,i);let f=[];Object.entries(t).forEach(([r,s])=>{let o=e[r];if(!o)return f.push({property:r,previousValue:o,currentValue:s,status:r in e?o===s?u.EQUAL:u.UPDATED:u.ADDED});if(p(s)){let c=h(o,s,i),a=m(c);return f.push({property:r,previousValue:o,currentValue:s,status:a,...a!==u.EQUAL&&{subPropertiesDiff:c}})}return f.push({property:r,previousValue:o,currentValue:s,status:g(o,s,i)})});let n=S(e,t);return n&&n.forEach(r=>{f.push({property:r.property,previousValue:r.value,currentValue:void 0,status:u.DELETED});}),i.showOnly&&i.showOnly.statuses.length>0?{type:"object",status:A(f),diff:b(f,i.showOnly)}:{type:"object",status:A(f),diff:f}}function I(e,t=[]){return e.filter(i=>t?.includes(i.status))}function L(e,t,i={showOnly:[]}){let f=e.map((n,r)=>({value:n,prevIndex:t===d.ADDED?null:r,newIndex:t===d.ADDED?r:null,indexDiff:null,status:t}));return i.showOnly&&i.showOnly.length>0?{type:"list",status:t,diff:f.filter(n=>i.showOnly?.includes(n.status))}:{type:"list",status:t,diff:f}}function T(e){return e.some(t=>t.status!==d.EQUAL)?d.UPDATED:d.EQUAL}function P(e,t){return p(e)&&t?Object.hasOwn(e,t):!1}var R=(e,t,i={showOnly:[],referenceProperty:void 0,considerMoveAsUpdate:!1})=>{if(!e&&!t)return {type:"list",status:d.EQUAL,diff:[]};if(!e)return L(t,d.ADDED,i);if(!t)return L(e,d.DELETED,i);let f=[],n=[];return t.forEach((r,s)=>{let o=e.findIndex((a,E)=>P(a,i.referenceProperty)?p(r)?D(a[i.referenceProperty],r[i.referenceProperty])&&!n.includes(E):!1:D(a,r)&&!n.includes(E));o>-1&&n.push(o);let c=o===-1?null:s-o;if(c===0){let a=d.EQUAL;return P(r,i.referenceProperty)&&(D(e[o],r)||(a=d.UPDATED)),f.push({value:r,prevIndex:o,newIndex:s,indexDiff:c,status:a})}return o===-1?f.push({value:r,prevIndex:null,newIndex:s,indexDiff:c,status:d.ADDED}):f.push({value:r,prevIndex:o,newIndex:s,indexDiff:c,status:i.considerMoveAsUpdate?d.UPDATED:d.MOVED})}),e.forEach((r,s)=>{if(!n.includes(s))return f.splice(s,0,{value:r,prevIndex:s,newIndex:null,indexDiff:null,status:d.DELETED})}),i.showOnly&&i?.showOnly?.length>0?{type:"list",status:T(f),diff:I(f,i.showOnly)}:{type:"list",status:T(f),diff:f}};
export { R as getListDiff, w as getObjectDiff, a as isEqual, p as isObject }; export { R as getListDiff, w as getObjectDiff, D as isEqual, p as isObject };

4
package-lock.json generated

@ -1,12 +1,12 @@
{ {
"name": "@donedeal0/superdiff", "name": "@donedeal0/superdiff",
"version": "1.0.9", "version": "1.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@donedeal0/superdiff", "name": "@donedeal0/superdiff",
"version": "1.0.9", "version": "1.1.0",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@babel/preset-env": "^7.23.8", "@babel/preset-env": "^7.23.8",

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "@donedeal0/superdiff", "name": "@donedeal0/superdiff",
"version": "1.1.0", "version": "1.1.1",
"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",

13
src/list-diff.ts

@ -60,12 +60,19 @@ function isReferencedObject(
* Returns the diff between two arrays * Returns the diff between two arrays
* @param {Array<T>} prevList - The original array. * @param {Array<T>} prevList - The original array.
* @param {Array<T>} nextList - The new array. * @param {Array<T>} nextList - The new array.
* @param {ListOptions} options - Options to refine your output.
- `showOnly` gives you the option to return only the values whose status you are interested in (e.g. `["added", "equal"]`).
- `referenceProperty` will consider an object to be updated instead of added or deleted if one of its properties remains stable, such as its `id`. This option has no effect on other datatypes.
* @returns ListDiff * @returns ListDiff
*/ */
export const getListDiff = <T>( export const getListDiff = <T>(
prevList: T[] | undefined | null, prevList: T[] | undefined | null,
nextList: T[] | undefined | null, nextList: T[] | undefined | null,
options: ListOptions = { showOnly: [], referenceProperty: undefined } options: ListOptions = {
showOnly: [],
referenceProperty: undefined,
considerMoveAsUpdate: false,
}
): ListDiff => { ): ListDiff => {
if (!prevList && !nextList) { if (!prevList && !nextList) {
return { return {
@ -132,7 +139,9 @@ export const getListDiff = <T>(
prevIndex, prevIndex,
newIndex: i, newIndex: i,
indexDiff, indexDiff,
status: LIST_STATUS.MOVED, status: options.considerMoveAsUpdate
? LIST_STATUS.UPDATED
: LIST_STATUS.MOVED,
}); });
}); });

1
src/model.ts

@ -54,6 +54,7 @@ export type ObjectOptions = {
export type ListOptions = { export type ListOptions = {
showOnly?: Array<ListStatusTuple[number]>; showOnly?: Array<ListStatusTuple[number]>;
referenceProperty?: string; referenceProperty?: string;
considerMoveAsUpdate?: boolean;
}; };
export type ListDiff = { export type ListDiff = {

5
src/object-diff.ts

@ -224,6 +224,11 @@ function getSubPropertiesDiff(
* Returns the diff between two objects * Returns the diff between two objects
* @param {Record<string, any>} prevData - The original object. * @param {Record<string, any>} prevData - The original object.
* @param {Record<string, any>} nextData - The new object. * @param {Record<string, any>} nextData - The new object.
* * @param {ListOptions} 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).
- `ignoreArrayOrder` if set to `true`, `["hello", "world"]` and `["world", "hello"]` will be treated as `equal`, because the two arrays have the same value, just not in the same order.
* @returns ObjectDiff * @returns ObjectDiff
*/ */
export function getObjectDiff( export function getObjectDiff(

110
test/list-diff.test.ts

@ -587,4 +587,114 @@ describe("getListDiff", () => {
], ],
}); });
}); });
it("consider moved values as updated if the considerMoveAsUpdate option is true", () => {
expect(
getListDiff(["mbappe", "messi"], ["mbappe", "mbappe", "messi"], {
considerMoveAsUpdate: true,
})
).toStrictEqual({
type: "list",
status: "updated",
diff: [
{
value: "mbappe",
prevIndex: 0,
newIndex: 0,
indexDiff: 0,
status: "equal",
},
{
value: "mbappe",
prevIndex: null,
newIndex: 1,
indexDiff: null,
status: "added",
},
{
value: "messi",
prevIndex: 1,
newIndex: 2,
indexDiff: 1,
status: "updated",
},
],
});
expect(
getListDiff(
[
"hello",
{ id: 37, isCool: true, hobbies: ["golf", "ski"] },
{ id: 38, isCool: false, hobbies: ["football"] },
undefined,
{ id: 8, age: 77 },
{ id: 55, character: { strength: 66 } },
],
[
{ id: 8, age: 77 },
{ id: 37, isCool: false, hobbies: ["golf", "ski"] },
{ id: 38, isCool: false, hobbies: ["football"] },
undefined,
{ id: 99, character: { strength: 69 } },
],
{
referenceProperty: "id",
considerMoveAsUpdate: true,
}
)
).toStrictEqual({
type: "list",
status: "updated",
diff: [
{
value: "hello",
prevIndex: 0,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: { id: 8, age: 77 },
prevIndex: 4,
newIndex: 0,
indexDiff: -4,
status: "updated",
},
{
value: { id: 37, isCool: false, hobbies: ["golf", "ski"] },
prevIndex: 1,
newIndex: 1,
indexDiff: 0,
status: "updated",
},
{
value: { id: 38, isCool: false, hobbies: ["football"] },
prevIndex: 2,
newIndex: 2,
indexDiff: 0,
status: "equal",
},
{
value: undefined,
prevIndex: 3,
newIndex: 3,
indexDiff: 0,
status: "equal",
},
{
value: { id: 55, character: { strength: 66 } },
prevIndex: 5,
newIndex: null,
indexDiff: null,
status: "deleted",
},
{
value: { id: 99, character: { strength: 69 } },
prevIndex: null,
newIndex: 4,
indexDiff: null,
status: "added",
},
],
});
});
}); });

Loading…
Cancel
Save