Skip to content

Instantly share code, notes, and snippets.

@JobLeonard
Last active September 26, 2017 22:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JobLeonard/c4292594ce4d439ab20ec109f315db3f to your computer and use it in GitHub Desktop.
Save JobLeonard/c4292594ce4d439ab20ec109f315db3f to your computer and use it in GitHub Desktop.
A dumb merge function for updating simple state trees in redux
/**
* 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