From 1838514a77ca379d35bc5b95be1520acdaf2db4d Mon Sep 17 00:00:00 2001 From: antoine lanoe Date: Thu, 12 Jan 2023 16:46:35 +0100 Subject: [PATCH] feat: fix: false values equality --- README.md | 1 + dist/index.js | 4 +-- dist/index.mjs | 4 +-- package.json | 2 +- src/object-diff.ts | 24 ++++++++++----- test/object-diff.test.ts | 65 +++++++++++++++++++++++++++++++++++++++- 6 files changed, 87 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 45c3b0c..4217121 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,7 @@ type ObjectDiff = { previousValue: any; currentValue: any; status: "added" | "deleted" | "equal" | "moved" | "updated"; + // only appears if some subproperties have been added/deleted/updated subPropertiesDiff?: { name: string; previousValue: any; diff --git a/dist/index.js b/dist/index.js index a53bca4..78943e6 100644 --- a/dist/index.js +++ b/dist/index.js @@ -1,8 +1,8 @@ 'use strict'; -var i={ADDED:"added",EQUAL:"equal",MOVED:"moved",DELETED:"deleted",UPDATED:"updated"};function a(e,t,n={discardArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)?e.length!==t.length?!1:n.discardArrayOrder?e.every(r=>t.some(f=>JSON.stringify(f)===JSON.stringify(r))):e.every((r,f)=>JSON.stringify(r)===JSON.stringify(t[f])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function d(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function A(e){return e.some(t=>t.status!==i.EQUAL)?i.UPDATED:i.EQUAL}function l(e,t){if(!e)return {type:"object",status:i.isEqual,diff:[]};let n=[];return Object.entries(e).forEach(([r,f])=>{if(d(f)){let u=[];return Object.entries(f).forEach(([s,o])=>{u.push({name:s,previousValue:t===i.ADDED?void 0:o,currentValue:t===i.ADDED?o:void 0,status:t});}),n.push({property:r,previousValue:t===i.ADDED?void 0:e[r],currentValue:t===i.ADDED?f:void 0,status:t,subPropertiesDiff:u})}return n.push({property:r,previousValue:t===i.ADDED?void 0:e[r],currentValue:t===i.ADDED?f:void 0,status:t})}),{type:"object",status:t,diff:n}}function S(e,t,n){if(!e)return;let r=Object.entries(e).find(([f])=>a(f,t,n));return r?r[1]:void 0}function E(e,t,n){return a(e,t,n)?i.EQUAL:i.UPDATED}function j(e){return e.some(t=>t.status!==i.EQUAL)?i.UPDATED:i.EQUAL}function y(e,t){if(!e)return;let n=Object.keys(e),r=Object.keys(t),f=n.filter(u=>!r.includes(u));if(f.length>0)return f.map(u=>({property:u,value:e[u]}))}function O(e,t,n){let r=[],f,u=y(e,t);return u&&u.forEach(s=>{r.push({name:s.property,previousValue:s.value,currentValue:void 0,status:i.DELETED});}),Object.entries(t).forEach(([s,o])=>{let D=S(e,s,n);if(!D&&!!s)return r.push({name:s,previousValue:void 0,currentValue:o,status:i.ADDED});if(d(o)){let c=O(D,o,n);c&&c.length>0&&(f=c);}D&&r.push({name:s,previousValue:D,currentValue:o,status:E(D,o,n),...!!f&&{subDiff:f}});}),r}function g(e,t,n){if(!e&&!t)return {type:"object",status:i.EQUAL,diff:[]};if(!e)return l(t,i.ADDED);if(!t)return l(e,i.DELETED);let r=[];Object.entries(t).forEach(([u,s])=>{let o=e[u];if(!o)return r.push({property:u,previousValue:void 0,currentValue:s,status:i.ADDED});if(d(s)){let D=O(o,s,n);return r.push({property:u,previousValue:o,currentValue:s,status:j(D),subPropertiesDiff:D})}return r.push({property:u,previousValue:o,currentValue:s,status:E(o,s,n)})});let f=y(e,t);return f&&f.forEach(u=>{r.push({property:u.property,previousValue:u.value,currentValue:void 0,status:i.DELETED});}),{type:"object",status:A(r),diff:r}}function b(e,t){return {type:"list",status:t,diff:e.map((n,r)=>({value:n,prevIndex:t===i.ADDED?null:r,newIndex:t===i.ADDED?r:null,indexDiff:null,status:t}))}}function m(e){return e.some(t=>t.status!==i.EQUAL)?i.UPDATED:i.EQUAL}var L=(e,t)=>{if(!e&&!t)return {type:"list",status:i.EQUAL,diff:[]};if(!e)return b(t,i.ADDED);if(!t)return b(e,i.DELETED);let n=[];return t.forEach((r,f)=>{let u=e.findIndex(o=>a(o,r)),s=u===-1?null:f-u;return s===0?n.push({value:r,prevIndex:u,newIndex:f,indexDiff:s,status:i.EQUAL}):u===-1?n.push({value:r,prevIndex:null,newIndex:f,indexDiff:s,status:i.ADDED}):n.push({value:r,prevIndex:u,newIndex:f,indexDiff:s,status:i.MOVED})}),e.forEach((r,f)=>{if(!t.some(u=>a(u,r)))return n.splice(f,0,{value:r,prevIndex:f,newIndex:null,indexDiff:null,status:i.DELETED})}),{type:"list",status:m(n),diff:n}}; +var r={ADDED:"added",EQUAL:"equal",MOVED:"moved",DELETED:"deleted",UPDATED:"updated"};function a(e,t,n={discardArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)?e.length!==t.length?!1:n.discardArrayOrder?e.every(i=>t.some(f=>JSON.stringify(f)===JSON.stringify(i))):e.every((i,f)=>JSON.stringify(i)===JSON.stringify(t[f])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function p(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function A(e){return e.some(t=>t.status!==r.EQUAL)?r.UPDATED:r.EQUAL}function E(e,t){if(!e)return {type:"object",status:r.isEqual,diff:[]};let n=[];return Object.entries(e).forEach(([i,f])=>{if(p(f)){let s=[];return Object.entries(f).forEach(([u,o])=>{s.push({name:u,previousValue:t===r.ADDED?void 0:o,currentValue:t===r.ADDED?o:void 0,status:t});}),n.push({property:i,previousValue:t===r.ADDED?void 0:e[i],currentValue:t===r.ADDED?f:void 0,status:t,subPropertiesDiff:s})}return n.push({property:i,previousValue:t===r.ADDED?void 0:e[i],currentValue:t===r.ADDED?f:void 0,status:t})}),{type:"object",status:t,diff:n}}function S(e,t,n){if(!e)return;let i=Object.entries(e).find(([f])=>a(f,t,n));return i?i[1]:void 0}function l(e,t,n){return a(e,t,n)?r.EQUAL:r.UPDATED}function j(e){return e.some(t=>t.status!==r.EQUAL)?r.UPDATED:r.EQUAL}function y(e,t){if(!e)return;let n=Object.keys(e),i=Object.keys(t),f=n.filter(s=>!i.includes(s));if(f.length>0)return f.map(s=>({property:s,value:e[s]}))}function O(e,t,n){let i=[],f,s=y(e,t);return s&&s.forEach(u=>{i.push({name:u.property,previousValue:u.value,currentValue:void 0,status:r.DELETED});}),Object.entries(t).forEach(([u,o])=>{let D=S(e,u,n);if(!D)return i.push({name:u,previousValue:D,currentValue:o,status:!e||!(u in e)?r.ADDED:D===o?r.EQUAL:r.UPDATED});if(p(o)){let d=O(D,o,n);d&&d.length>0&&(f=d);}D&&i.push({name:u,previousValue:D,currentValue:o,status:l(D,o,n),...!!f&&{subDiff:f}});}),i}function g(e,t,n){if(!e&&!t)return {type:"object",status:r.EQUAL,diff:[]};if(!e)return E(t,r.ADDED);if(!t)return E(e,r.DELETED);let i=[];Object.entries(t).forEach(([s,u])=>{let o=e[s];if(!o)return i.push({property:s,previousValue:o,currentValue:u,status:s in e?o===u?r.EQUAL:r.UPDATED:r.ADDED});if(p(u)){let D=O(o,u,n),d=j(D);return i.push({property:s,previousValue:o,currentValue:u,status:d,...d!==r.EQUAL&&{subPropertiesDiff:D}})}return i.push({property:s,previousValue:o,currentValue:u,status:l(o,u,n)})});let f=y(e,t);return f&&f.forEach(s=>{i.push({property:s.property,previousValue:s.value,currentValue:void 0,status:r.DELETED});}),{type:"object",status:A(i),diff:i}}function b(e,t){return {type:"list",status:t,diff:e.map((n,i)=>({value:n,prevIndex:t===r.ADDED?null:i,newIndex:t===r.ADDED?i:null,indexDiff:null,status:t}))}}function m(e){return e.some(t=>t.status!==r.EQUAL)?r.UPDATED:r.EQUAL}var L=(e,t)=>{if(!e&&!t)return {type:"list",status:r.EQUAL,diff:[]};if(!e)return b(t,r.ADDED);if(!t)return b(e,r.DELETED);let n=[];return t.forEach((i,f)=>{let s=e.findIndex(o=>a(o,i)),u=s===-1?null:f-s;return u===0?n.push({value:i,prevIndex:s,newIndex:f,indexDiff:u,status:r.EQUAL}):s===-1?n.push({value:i,prevIndex:null,newIndex:f,indexDiff:u,status:r.ADDED}):n.push({value:i,prevIndex:s,newIndex:f,indexDiff:u,status:r.MOVED})}),e.forEach((i,f)=>{if(!t.some(s=>a(s,i)))return n.splice(f,0,{value:i,prevIndex:f,newIndex:null,indexDiff:null,status:r.DELETED})}),{type:"list",status:m(n),diff:n}}; exports.getListDiff = L; exports.getObjectDiff = g; exports.isEqual = a; -exports.isObject = d; +exports.isObject = p; diff --git a/dist/index.mjs b/dist/index.mjs index f69aabc..7e38af8 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -1,3 +1,3 @@ -var i={ADDED:"added",EQUAL:"equal",MOVED:"moved",DELETED:"deleted",UPDATED:"updated"};function a(e,t,n={discardArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)?e.length!==t.length?!1:n.discardArrayOrder?e.every(r=>t.some(f=>JSON.stringify(f)===JSON.stringify(r))):e.every((r,f)=>JSON.stringify(r)===JSON.stringify(t[f])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function d(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function S(e){return e.some(t=>t.status!==i.EQUAL)?i.UPDATED:i.EQUAL}function E(e,t){if(!e)return {type:"object",status:i.isEqual,diff:[]};let n=[];return Object.entries(e).forEach(([r,f])=>{if(d(f)){let u=[];return Object.entries(f).forEach(([s,o])=>{u.push({name:s,previousValue:t===i.ADDED?void 0:o,currentValue:t===i.ADDED?o:void 0,status:t});}),n.push({property:r,previousValue:t===i.ADDED?void 0:e[r],currentValue:t===i.ADDED?f:void 0,status:t,subPropertiesDiff:u})}return n.push({property:r,previousValue:t===i.ADDED?void 0:e[r],currentValue:t===i.ADDED?f:void 0,status:t})}),{type:"object",status:t,diff:n}}function j(e,t,n){if(!e)return;let r=Object.entries(e).find(([f])=>a(f,t,n));return r?r[1]:void 0}function y(e,t,n){return a(e,t,n)?i.EQUAL:i.UPDATED}function g(e){return e.some(t=>t.status!==i.EQUAL)?i.UPDATED:i.EQUAL}function O(e,t){if(!e)return;let n=Object.keys(e),r=Object.keys(t),f=n.filter(u=>!r.includes(u));if(f.length>0)return f.map(u=>({property:u,value:e[u]}))}function b(e,t,n){let r=[],f,u=O(e,t);return u&&u.forEach(s=>{r.push({name:s.property,previousValue:s.value,currentValue:void 0,status:i.DELETED});}),Object.entries(t).forEach(([s,o])=>{let D=j(e,s,n);if(!D&&!!s)return r.push({name:s,previousValue:void 0,currentValue:o,status:i.ADDED});if(d(o)){let l=b(D,o,n);l&&l.length>0&&(f=l);}D&&r.push({name:s,previousValue:D,currentValue:o,status:y(D,o,n),...!!f&&{subDiff:f}});}),r}function m(e,t,n){if(!e&&!t)return {type:"object",status:i.EQUAL,diff:[]};if(!e)return E(t,i.ADDED);if(!t)return E(e,i.DELETED);let r=[];Object.entries(t).forEach(([u,s])=>{let o=e[u];if(!o)return r.push({property:u,previousValue:void 0,currentValue:s,status:i.ADDED});if(d(s)){let D=b(o,s,n);return r.push({property:u,previousValue:o,currentValue:s,status:g(D),subPropertiesDiff:D})}return r.push({property:u,previousValue:o,currentValue:s,status:y(o,s,n)})});let f=O(e,t);return f&&f.forEach(u=>{r.push({property:u.property,previousValue:u.value,currentValue:void 0,status:i.DELETED});}),{type:"object",status:S(r),diff:r}}function A(e,t){return {type:"list",status:t,diff:e.map((n,r)=>({value:n,prevIndex:t===i.ADDED?null:r,newIndex:t===i.ADDED?r:null,indexDiff:null,status:t}))}}function L(e){return e.some(t=>t.status!==i.EQUAL)?i.UPDATED:i.EQUAL}var h=(e,t)=>{if(!e&&!t)return {type:"list",status:i.EQUAL,diff:[]};if(!e)return A(t,i.ADDED);if(!t)return A(e,i.DELETED);let n=[];return t.forEach((r,f)=>{let u=e.findIndex(o=>a(o,r)),s=u===-1?null:f-u;return s===0?n.push({value:r,prevIndex:u,newIndex:f,indexDiff:s,status:i.EQUAL}):u===-1?n.push({value:r,prevIndex:null,newIndex:f,indexDiff:s,status:i.ADDED}):n.push({value:r,prevIndex:u,newIndex:f,indexDiff:s,status:i.MOVED})}),e.forEach((r,f)=>{if(!t.some(u=>a(u,r)))return n.splice(f,0,{value:r,prevIndex:f,newIndex:null,indexDiff:null,status:i.DELETED})}),{type:"list",status:L(n),diff:n}}; +var r={ADDED:"added",EQUAL:"equal",MOVED:"moved",DELETED:"deleted",UPDATED:"updated"};function a(e,t,n={discardArrayOrder:!1}){return typeof e!=typeof t?!1:Array.isArray(e)?e.length!==t.length?!1:n.discardArrayOrder?e.every(i=>t.some(f=>JSON.stringify(f)===JSON.stringify(i))):e.every((i,f)=>JSON.stringify(i)===JSON.stringify(t[f])):typeof e=="object"?JSON.stringify(e)===JSON.stringify(t):e===t}function p(e){return !!e&&typeof e=="object"&&!Array.isArray(e)}function S(e){return e.some(t=>t.status!==r.EQUAL)?r.UPDATED:r.EQUAL}function l(e,t){if(!e)return {type:"object",status:r.isEqual,diff:[]};let n=[];return Object.entries(e).forEach(([i,f])=>{if(p(f)){let s=[];return Object.entries(f).forEach(([u,o])=>{s.push({name:u,previousValue:t===r.ADDED?void 0:o,currentValue:t===r.ADDED?o:void 0,status:t});}),n.push({property:i,previousValue:t===r.ADDED?void 0:e[i],currentValue:t===r.ADDED?f:void 0,status:t,subPropertiesDiff:s})}return n.push({property:i,previousValue:t===r.ADDED?void 0:e[i],currentValue:t===r.ADDED?f:void 0,status:t})}),{type:"object",status:t,diff:n}}function j(e,t,n){if(!e)return;let i=Object.entries(e).find(([f])=>a(f,t,n));return i?i[1]:void 0}function y(e,t,n){return a(e,t,n)?r.EQUAL:r.UPDATED}function g(e){return e.some(t=>t.status!==r.EQUAL)?r.UPDATED:r.EQUAL}function O(e,t){if(!e)return;let n=Object.keys(e),i=Object.keys(t),f=n.filter(s=>!i.includes(s));if(f.length>0)return f.map(s=>({property:s,value:e[s]}))}function b(e,t,n){let i=[],f,s=O(e,t);return s&&s.forEach(u=>{i.push({name:u.property,previousValue:u.value,currentValue:void 0,status:r.DELETED});}),Object.entries(t).forEach(([u,o])=>{let D=j(e,u,n);if(!D)return i.push({name:u,previousValue:D,currentValue:o,status:!e||!(u in e)?r.ADDED:D===o?r.EQUAL:r.UPDATED});if(p(o)){let d=b(D,o,n);d&&d.length>0&&(f=d);}D&&i.push({name:u,previousValue:D,currentValue:o,status:y(D,o,n),...!!f&&{subDiff:f}});}),i}function m(e,t,n){if(!e&&!t)return {type:"object",status:r.EQUAL,diff:[]};if(!e)return l(t,r.ADDED);if(!t)return l(e,r.DELETED);let i=[];Object.entries(t).forEach(([s,u])=>{let o=e[s];if(!o)return i.push({property:s,previousValue:o,currentValue:u,status:s in e?o===u?r.EQUAL:r.UPDATED:r.ADDED});if(p(u)){let D=b(o,u,n),d=g(D);return i.push({property:s,previousValue:o,currentValue:u,status:d,...d!==r.EQUAL&&{subPropertiesDiff:D}})}return i.push({property:s,previousValue:o,currentValue:u,status:y(o,u,n)})});let f=O(e,t);return f&&f.forEach(s=>{i.push({property:s.property,previousValue:s.value,currentValue:void 0,status:r.DELETED});}),{type:"object",status:S(i),diff:i}}function A(e,t){return {type:"list",status:t,diff:e.map((n,i)=>({value:n,prevIndex:t===r.ADDED?null:i,newIndex:t===r.ADDED?i:null,indexDiff:null,status:t}))}}function L(e){return e.some(t=>t.status!==r.EQUAL)?r.UPDATED:r.EQUAL}var h=(e,t)=>{if(!e&&!t)return {type:"list",status:r.EQUAL,diff:[]};if(!e)return A(t,r.ADDED);if(!t)return A(e,r.DELETED);let n=[];return t.forEach((i,f)=>{let s=e.findIndex(o=>a(o,i)),u=s===-1?null:f-s;return u===0?n.push({value:i,prevIndex:s,newIndex:f,indexDiff:u,status:r.EQUAL}):s===-1?n.push({value:i,prevIndex:null,newIndex:f,indexDiff:u,status:r.ADDED}):n.push({value:i,prevIndex:s,newIndex:f,indexDiff:u,status:r.MOVED})}),e.forEach((i,f)=>{if(!t.some(s=>a(s,i)))return n.splice(f,0,{value:i,prevIndex:f,newIndex:null,indexDiff:null,status:r.DELETED})}),{type:"list",status:L(n),diff:n}}; -export { h as getListDiff, m as getObjectDiff, a as isEqual, d as isObject }; +export { h as getListDiff, m as getObjectDiff, a as isEqual, p as isObject }; diff --git a/package.json b/package.json index 2b6b2d8..c3c7b52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@donedeal0/superdiff", - "version": "1.0.6", + "version": "1.0.7", "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/object-diff.ts b/src/object-diff.ts index cd2e83e..eb224ef 100644 --- a/src/object-diff.ts +++ b/src/object-diff.ts @@ -134,12 +134,17 @@ function getSubPropertiesDiff( nextSubProperty, options ); - if (!!!previousMatch && !!nextSubProperty) { + if (!!!previousMatch) { return subPropertiesDiff.push({ name: nextSubProperty, - previousValue: undefined, + previousValue: previousMatch, currentValue: nextSubValue, - status: STATUS.ADDED, + status: + !previousValue || !(nextSubProperty in previousValue) + ? STATUS.ADDED + : previousMatch === nextSubValue + ? STATUS.EQUAL + : STATUS.UPDATED, }); } if (isObject(nextSubValue)) { @@ -189,9 +194,13 @@ export function getObjectDiff( if (!!!previousValue) { return diff.push({ property: nextProperty, - previousValue: undefined, + previousValue, currentValue: nextValue, - status: STATUS.ADDED, + status: !(nextProperty in prevData) + ? STATUS.ADDED + : previousValue === nextValue + ? STATUS.EQUAL + : STATUS.UPDATED, }); } if (isObject(nextValue)) { @@ -200,12 +209,13 @@ export function getObjectDiff( nextValue, options ); + const subPropertyStatus = getPropertyStatus(subPropertiesDiff); return diff.push({ property: nextProperty, previousValue, currentValue: nextValue, - status: getPropertyStatus(subPropertiesDiff), - subPropertiesDiff, + status: subPropertyStatus, + ...(subPropertyStatus !== STATUS.EQUAL && { subPropertiesDiff }), }); } return diff.push({ diff --git a/test/object-diff.test.ts b/test/object-diff.test.ts index 36cc1d7..b01a348 100644 --- a/test/object-diff.test.ts +++ b/test/object-diff.test.ts @@ -71,6 +71,69 @@ describe("getObjectDiff", () => { ], }); }); + it("consider objects as equal if no changes are detected", () => { + expect( + getObjectDiff( + { + age: 66, + member: false, + promoCode: null, + city: undefined, + hobbies: ["golf", "football"], + options: { vegan: undefined, phone: null }, + }, + { + age: 66, + member: false, + promoCode: null, + city: undefined, + hobbies: ["golf", "football"], + options: { vegan: undefined, phone: null }, + } + ) + ).toStrictEqual({ + type: "object", + status: "equal", + diff: [ + { + property: "age", + previousValue: 66, + currentValue: 66, + status: "equal", + }, + { + property: "member", + previousValue: false, + currentValue: false, + status: "equal", + }, + { + property: "promoCode", + previousValue: null, + currentValue: null, + status: "equal", + }, + { + property: "city", + previousValue: undefined, + currentValue: undefined, + status: "equal", + }, + { + property: "hobbies", + previousValue: ["golf", "football"], + currentValue: ["golf", "football"], + status: "equal", + }, + { + property: "options", + previousValue: { vegan: undefined, phone: null }, + currentValue: { vegan: undefined, phone: null }, + status: "equal", + }, + ], + }); + }); it("detects changed between two objects", () => { expect( getObjectDiff( @@ -301,7 +364,7 @@ describe("getObjectDiff", () => { ], }); }); - it("detects changed between two objects BUT doesn't care about array order as long as all values are preserved", () => { + it("detects changed between two objects BUT doesn't care about array order as long as all values are preserved when discardArrayOrder option is activated", () => { expect( getObjectDiff( {