diff --git a/dist/index.d.mts b/dist/index.d.mts index 11c580c..f95a123 100644 --- a/dist/index.d.mts +++ b/dist/index.d.mts @@ -29,6 +29,7 @@ type ObjectOptions = { type ListOptions = { showOnly?: Array; referenceProperty?: string; + considerMoveAsUpdate?: boolean; }; type ListDiff = { type: "list"; @@ -64,6 +65,11 @@ type ObjectDiff = { * 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. + - `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 */ 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 * @param {Array} prevList - The original array. * @param {Array} 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 */ declare const getListDiff: (prevList: T[] | null | undefined, nextList: T[] | null | undefined, options?: ListOptions) => ListDiff; diff --git a/dist/index.d.ts b/dist/index.d.ts index 11c580c..f95a123 100644 --- a/dist/index.d.ts +++ b/dist/index.d.ts @@ -29,6 +29,7 @@ type ObjectOptions = { type ListOptions = { showOnly?: Array; referenceProperty?: string; + considerMoveAsUpdate?: boolean; }; type ListDiff = { type: "list"; @@ -64,6 +65,11 @@ type ObjectDiff = { * 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. + - `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 */ 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 * @param {Array} prevList - The original array. * @param {Array} 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 */ declare const getListDiff: (prevList: T[] | null | undefined, nextList: T[] | null | undefined, options?: ListOptions) => ListDiff; diff --git a/dist/index.js b/dist/index.js index b1f87d2..07d976e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,8 +1,8 @@ '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.getObjectDiff = m; -exports.isEqual = a; +exports.isEqual = D; exports.isObject = p; diff --git a/dist/index.mjs b/dist/index.mjs index ee0d4c8..83cfa3b 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -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 }; diff --git a/package-lock.json b/package-lock.json index d49dc64..8209f7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@donedeal0/superdiff", - "version": "1.0.9", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@donedeal0/superdiff", - "version": "1.0.9", + "version": "1.1.0", "license": "ISC", "devDependencies": { "@babel/preset-env": "^7.23.8", diff --git a/package.json b/package.json index 3960916..a9a9259 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "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", "main": "dist/index.js", "module": "dist/index.js", diff --git a/src/list-diff.ts b/src/list-diff.ts index c23e243..ffaf55e 100644 --- a/src/list-diff.ts +++ b/src/list-diff.ts @@ -60,12 +60,19 @@ function isReferencedObject( * Returns the diff between two arrays * @param {Array} prevList - The original array. * @param {Array} 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 */ export const getListDiff = ( prevList: T[] | undefined | null, nextList: T[] | undefined | null, - options: ListOptions = { showOnly: [], referenceProperty: undefined } + options: ListOptions = { + showOnly: [], + referenceProperty: undefined, + considerMoveAsUpdate: false, + } ): ListDiff => { if (!prevList && !nextList) { return { @@ -132,7 +139,9 @@ export const getListDiff = ( prevIndex, newIndex: i, indexDiff, - status: LIST_STATUS.MOVED, + status: options.considerMoveAsUpdate + ? LIST_STATUS.UPDATED + : LIST_STATUS.MOVED, }); }); diff --git a/src/model.ts b/src/model.ts index 3b6edfa..ea17db8 100644 --- a/src/model.ts +++ b/src/model.ts @@ -54,6 +54,7 @@ export type ObjectOptions = { export type ListOptions = { showOnly?: Array; referenceProperty?: string; + considerMoveAsUpdate?: boolean; }; export type ListDiff = { diff --git a/src/object-diff.ts b/src/object-diff.ts index 2515ff0..453589d 100644 --- a/src/object-diff.ts +++ b/src/object-diff.ts @@ -224,6 +224,11 @@ 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. + - `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 */ export function getObjectDiff( diff --git a/test/list-diff.test.ts b/test/list-diff.test.ts index 7f7b498..e52d410 100644 --- a/test/list-diff.test.ts +++ b/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", + }, + ], + }); + }); });