@broofa/merge
Advanced tools
Comparing version 1.0.1 to 1.0.2
53
index.js
@@ -1,45 +0,46 @@ | ||
function merge(a, b) { | ||
/** | ||
* @param {primitive} before | ||
* @param {primitive} after | ||
*/ | ||
function merge(before, after) { | ||
// Identical? | ||
if (a === b) return a; | ||
if (before === after) return before; | ||
// Undefined or null? | ||
if (a == null || b == null) return b; | ||
if (before == null || after == null) return after; | ||
// Different types? | ||
if (a.constructor !== b.constructor) return b; | ||
if (before.constructor !== after.constructor) return after; | ||
let type = typeof(a); | ||
if (a.getTime) type = 'date' // Not JSON, but useful | ||
if (Array.isArray(a)) type = 'array'; | ||
let type = before.constructor.name; | ||
switch (type) { | ||
// '===' comparable tyeps | ||
case 'boolean': | ||
case 'number': | ||
case 'string': // Not strictly JSON but useful | ||
return a === b ? a : b; | ||
case 'Boolean': | ||
case 'Number': | ||
case 'String': | ||
case 'Symbol': | ||
return before === after ? before : after; | ||
case 'date': | ||
return a.getTime() === b.getTime() ? a : b; | ||
case 'Date': // Not strictly JSON but useful | ||
return before.getTime() === after.getTime() ? before : after; | ||
case 'object': { | ||
case 'Object': { | ||
let isEqual = true; | ||
const merged = {}; | ||
for (const k of new Set([...Object.keys(a), ...Object.keys(b)])) { | ||
const val = merge(a[k], b[k]); | ||
isEqual = isEqual && val === a[k]; | ||
for (const k of new Set([...Object.keys(before), ...Object.keys(after)])) { | ||
const val = merge(before[k], after[k]); | ||
isEqual = isEqual && val === before[k]; | ||
if (val !== undefined) merged[k] = val; | ||
} | ||
return isEqual ? a : merged; | ||
return isEqual ? before : merged; | ||
} | ||
case 'array': { | ||
let isEqual = a.length === b.length; | ||
const merged = new Array(Math.max(a.length, b.length)); | ||
case 'Array': { | ||
let isEqual = before.length === after.length; | ||
const merged = new Array(Math.max(before.length, after.length)); | ||
for (let k = 0, l = merged.length; k < l; k++) { | ||
const val = merge(a[k], b[k]); | ||
isEqual = isEqual && val === a[k]; | ||
const val = merge(before[k], after[k]); | ||
isEqual = isEqual && val === before[k]; | ||
if (val !== undefined) merged[k] = val; | ||
} | ||
return isEqual ? a : merged; | ||
return isEqual ? before : merged; | ||
} | ||
@@ -46,0 +47,0 @@ |
{ | ||
"name": "@broofa/merge", | ||
"version": "1.0.1", | ||
"version": "1.0.2", | ||
"description": "Merge immutable JSON data structures to allow for identity (===) comparisons on deeply-equal subtrees", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -7,5 +7,13 @@ <!-- | ||
Merge immutable JSON data structures to allow for identity (===) comparisons on | ||
deeply-equal subtrees. | ||
Merge immutable model state (or any data structure, really), preserving references to unchanged nodes. This | ||
allows for `===` operation to determine where state has changed. (Useful when | ||
dealing with immutable data models such as React+Redux, where `===` is used for | ||
exactly this purpose.) | ||
In practical terms, where `state = merge(before, after)` the returned `state` object has the following properties: | ||
* `assert.deepEqual(state, after)` will always be true | ||
* `state[X] === before[X]` will be true where `deepEqual(before[X], after[X])` | ||
* `state[X] === after[X]` will be true where `after[X]` replaces all `before[X]` state | ||
## Installation | ||
@@ -23,35 +31,28 @@ | ||
const obj0 = { | ||
const before = { | ||
a: 'hello', | ||
b: 123, | ||
c: { | ||
ca: 'zig', | ||
cb: [{a:1}, {b:2}] | ||
}, | ||
c: {ca: ['zig'], cb: [{a:1}, {b:2}]}, | ||
}; | ||
const obj1 = { | ||
const after = { | ||
a: 'world', | ||
c: { | ||
ca: {a: 1}, | ||
cb: [{a:99}, {b:2}] | ||
}, | ||
b: 123, | ||
c: {ca: ['zig'], cb: [{a:99}, {b:2}]}, | ||
}; | ||
const result = merge(obj0, obj1); | ||
const state = merge(before, after); | ||
// Result will always be deepEqual to right-most argument | ||
assert.deepEqual(obj1, result); | ||
assert.deepEqual(after, state); // Always true | ||
// root.c has changed, so is not identical | ||
result.c === obj0.c; // ⇨ false | ||
// Where state HAS changed | ||
state === before; // ⇨ false | ||
state.c === before.c; // ⇨ false | ||
state.c.cb === before.c.cb; // ⇨ false | ||
state.c.cb[0] === before.c.cb[0]; // ⇨ false | ||
// ... same goes for root.c.ca and root.c.cb and root.c.cb[0] | ||
result.c.ca === obj0.c.ca; // ⇨ false | ||
result.c.cb === obj0.c.cb; // ⇨ false | ||
result.c.cb[0] === obj0.c.cb[0]; // ⇨ false | ||
// Where state HAS NOT changed | ||
state.c.ca === before.c.ca; // ⇨ true | ||
state.c.cb[1] === before.c.cb[1]; // ⇨ true | ||
// 'But root.c.cb[1] hasn't, so `===` works! | ||
result.c.cb[1] === obj0.c.cb[1]; // ⇨ true | ||
``` | ||
@@ -58,0 +59,0 @@ |
@@ -7,5 +7,13 @@ ```javascript --hide | ||
Merge immutable JSON data structures to allow for identity (===) comparisons on | ||
deeply-equal subtrees. | ||
Merge immutable model state (or any data structure, really), preserving references to unchanged nodes. This | ||
allows for `===` operation to determine where state has changed. (Useful when | ||
dealing with immutable data models such as React+Redux, where `===` is used for | ||
exactly this purpose.) | ||
In practical terms, where `state = merge(before, after)` the returned `state` object has the following properties: | ||
* `assert.deepEqual(state, after)` will always be true | ||
* `state[X] === before[X]` will be true where `deepEqual(before[X], after[X])` | ||
* `state[X] === after[X]` will be true where `after[X]` replaces all `before[X]` state | ||
## Installation | ||
@@ -23,34 +31,27 @@ | ||
const obj0 = { | ||
const before = { | ||
a: 'hello', | ||
b: 123, | ||
c: { | ||
ca: 'zig', | ||
cb: [{a:1}, {b:2}] | ||
}, | ||
c: {ca: ['zig'], cb: [{a:1}, {b:2}]}, | ||
}; | ||
const obj1 = { | ||
const after = { | ||
a: 'world', | ||
c: { | ||
ca: {a: 1}, | ||
cb: [{a:99}, {b:2}] | ||
}, | ||
b: 123, | ||
c: {ca: ['zig'], cb: [{a:99}, {b:2}]}, | ||
}; | ||
const result = merge(obj0, obj1); | ||
const state = merge(before, after); | ||
// Result will always be deepEqual to right-most argument | ||
assert.deepEqual(obj1, result); | ||
assert.deepEqual(after, state); // Always true | ||
// root.c has changed, so is not identical | ||
result.c === obj0.c; // RESULT | ||
// Where state HAS changed | ||
state === before; // RESULT | ||
state.c === before.c; // RESULT | ||
state.c.cb === before.c.cb; // RESULT | ||
state.c.cb[0] === before.c.cb[0]; // RESULT | ||
// ... same goes for root.c.ca and root.c.cb and root.c.cb[0] | ||
result.c.ca === obj0.c.ca; // RESULT | ||
result.c.cb === obj0.c.cb; // RESULT | ||
result.c.cb[0] === obj0.c.cb[0]; // RESULT | ||
// 'But root.c.cb[1] hasn't, so `===` works! | ||
result.c.cb[1] === obj0.c.cb[1]; // RESULT | ||
// Where state HAS NOT changed | ||
state.c.ca === before.c.ca; // RESULT | ||
state.c.cb[1] === before.c.cb[1]; // RESULT | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
6978
96
59