Browse Source

feat: fix: false values equality

pull/6/head
antoine lanoe 2 years ago committed by DoneDeal0
parent
commit
1838514a77
  1. 1
      README.md
  2. 4
      dist/index.js
  3. 4
      dist/index.mjs
  4. 2
      package.json
  5. 24
      src/object-diff.ts
  6. 65
      test/object-diff.test.ts

1
README.md

@ -154,6 +154,7 @@ type ObjectDiff = {
previousValue: any; previousValue: any;
currentValue: any; currentValue: any;
status: "added" | "deleted" | "equal" | "moved" | "updated"; status: "added" | "deleted" | "equal" | "moved" | "updated";
// only appears if some subproperties have been added/deleted/updated
subPropertiesDiff?: { subPropertiesDiff?: {
name: string; name: string;
previousValue: any; previousValue: any;

4
dist/index.js vendored

@ -1,8 +1,8 @@
'use strict'; '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.getListDiff = L;
exports.getObjectDiff = g; exports.getObjectDiff = g;
exports.isEqual = a; exports.isEqual = a;
exports.isObject = d; exports.isObject = p;

4
dist/index.mjs vendored

@ -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 };

2
package.json

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

24
src/object-diff.ts

@ -134,12 +134,17 @@ function getSubPropertiesDiff(
nextSubProperty, nextSubProperty,
options options
); );
if (!!!previousMatch && !!nextSubProperty) { if (!!!previousMatch) {
return subPropertiesDiff.push({ return subPropertiesDiff.push({
name: nextSubProperty, name: nextSubProperty,
previousValue: undefined, previousValue: previousMatch,
currentValue: nextSubValue, currentValue: nextSubValue,
status: STATUS.ADDED, status:
!previousValue || !(nextSubProperty in previousValue)
? STATUS.ADDED
: previousMatch === nextSubValue
? STATUS.EQUAL
: STATUS.UPDATED,
}); });
} }
if (isObject(nextSubValue)) { if (isObject(nextSubValue)) {
@ -189,9 +194,13 @@ export function getObjectDiff(
if (!!!previousValue) { if (!!!previousValue) {
return diff.push({ return diff.push({
property: nextProperty, property: nextProperty,
previousValue: undefined, previousValue,
currentValue: nextValue, currentValue: nextValue,
status: STATUS.ADDED, status: !(nextProperty in prevData)
? STATUS.ADDED
: previousValue === nextValue
? STATUS.EQUAL
: STATUS.UPDATED,
}); });
} }
if (isObject(nextValue)) { if (isObject(nextValue)) {
@ -200,12 +209,13 @@ export function getObjectDiff(
nextValue, nextValue,
options options
); );
const subPropertyStatus = getPropertyStatus(subPropertiesDiff);
return diff.push({ return diff.push({
property: nextProperty, property: nextProperty,
previousValue, previousValue,
currentValue: nextValue, currentValue: nextValue,
status: getPropertyStatus(subPropertiesDiff), status: subPropertyStatus,
subPropertiesDiff, ...(subPropertyStatus !== STATUS.EQUAL && { subPropertiesDiff }),
}); });
} }
return diff.push({ return diff.push({

65
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", () => { it("detects changed between two objects", () => {
expect( expect(
getObjectDiff( 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( expect(
getObjectDiff( getObjectDiff(
{ {

Loading…
Cancel
Save