Last active
September 26, 2017 22:30
-
-
Save JobLeonard/c4292594ce4d439ab20ec109f315db3f to your computer and use it in GitHub Desktop.
A dumb merge function for updating simple state trees in redux
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Returns a new object that merges the values of | |
* `newObj` into `oldObj`. Uses shallow copying. | |
* | |
* **WARNING: Do NOT pass cyclical objects! | |
* This includes React nodes!** | |
* | |
* **IMPORTANT:** util.merge does **NOT** behave like | |
* lodash merge! *TL;DR:* (typed) arrays are not merged | |
* but replaced by the newer array. | |
* | |
* - for duplicate keys, values that are objects are | |
* recursively merged (except (typed) arrays and `null`) | |
* - in all other cases, the value from `newObj` is | |
* assigned to the returned object (including (typed) arrays and `null`). | |
* | |
* @param {object} oldObj | |
* @param {object} newObj | |
*/ | |
export function merge(oldObj, newObj) { | |
if (!(oldObj || newObj)) { | |
// if neither are defined, return whatever newObj is | |
// (safe, since all falsy values are immutable values) | |
return newObj; | |
} else if (!oldObj) { | |
// we expect a new object (immutability guarantee), | |
// so if there is no oldObj, return a copy of newObj | |
return Object.assign({}, newObj); | |
} else if (!newObj) { | |
// we expect a new object (immutability guarantee), | |
// so if there is no newObj, return a copy of oldObj | |
return Object.assign({}, oldObj); | |
} | |
let untouchedKeys = Object.keys(oldObj), | |
newKeys = Object.keys(newObj), | |
overlappingKeys = disjointArrays(untouchedKeys, newKeys), | |
mergedObj = {}, | |
key = '', | |
i = overlappingKeys.length; | |
while (i--) { | |
key = overlappingKeys[i]; | |
let newVal = newObj[key]; | |
// merge object values by recursion, otherwise just assign new value | |
mergedObj[key] = ( | |
typeof newVal === 'object' && | |
newVal !== null && // avoid accidentally turning null into {} | |
!isArray(newVal) // typof returns object for arrays | |
) ? merge(oldObj[key], newVal) : newVal; | |
} | |
// directly assign all values that don't need merging | |
i = untouchedKeys.length; | |
while (i--) { | |
key = untouchedKeys[i]; | |
mergedObj[key] = oldObj[key]; | |
} | |
i = newKeys.length; | |
while (i--) { | |
key = newKeys[i]; | |
mergedObj[key] = newObj[key]; | |
} | |
return mergedObj; | |
} | |
// checks if an object is an array or typed array | |
export function isArray(obj) { | |
return obj instanceof Array || isTypedArray(obj); | |
} | |
export function isTypedArray(obj) { | |
return obj instanceof Uint8Array || | |
obj instanceof Float32Array || | |
obj instanceof Uint16Array || | |
obj instanceof Uint32Array || | |
obj instanceof Int32Array || | |
obj instanceof Float64Array || | |
obj instanceof Int8Array || | |
obj instanceof Uint8ClampedArray || | |
obj instanceof Int16Array; | |
} | |
/** | |
* Takes arrays `a` and `b`. | |
* Returns an array containing overlapping values, and | |
* mutates `a` and `b` such that they only contain their | |
* non-overlapping values. If there are any overlapping | |
* values, `a` and `b` might change order. If either | |
* array contains duplicates, the matching duplicates | |
* will be put in the returned array, and the rest in | |
* will remain in the original arrays. | |
* Example: | |
* - in: a = `[1, 2, 2, 3]`, b = `[2, 3, 4, 5, 6]` | |
* - out: `[2, 3]`; a = `[1, 2]`, b = `[5, 6, 4]` | |
* @param {x[]} a - will be mutated | |
* @param {x[]} b - will be mutated | |
* @returns {x[]} overlap - overlapping values | |
*/ | |
export function disjointArrays(a, b) { | |
// Having the larger array in the inner loop should | |
// be a little bit faster (less loop initialisations) | |
if (b.length < a.length) { | |
let t = a; | |
a = b; | |
b = t; | |
} | |
let overlap = [], i = a.length; | |
while (i--) { | |
let aVal = a[i]; | |
let j = b.length; | |
while (j--) { | |
let bVal = b[j]; | |
if (aVal === bVal) { | |
overlap.push(aVal); | |
a[i] = a[a.length - 1]; | |
a.pop(); | |
b[j] = b[b.length - 1]; | |
b.pop(); | |
break; | |
} | |
} | |
} | |
return overlap; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment