I've written a helper for combining objects (deep merge).
It works, and is flexible to add options (like uniq
and replace
).
Replace should replaces the any matching properties on the object (but spread in any existing properties).
The code below works, but I believe it could be optimized particularly as I could NOT find a way to remove empty keys without the use of JSON.parse(JSON.stringify
- it feels wrong to have to crawl back over the entire array to do so.
Using Lodash is not an option.
Examples
DATA = {foo:{i:{z:[1]},x:[1]}}
getDeepMerge(DATA, {add:{foo:{q:[1]}}}
// {foo:{i:{},x:[1], q:[1]}}getDeepMerge(DATA, {add:{foo:{}}, replace:true}
// {foo:{}}getDeepMerge(DATA, {add:{foo:{i:{}}} }, replace:true, removeEmpty:true}
// {add:{foo:{i:{}}, x[1]} }
Code
const removeKey = (key, {[key]: _, ...rest}) => rest;
const getNewObj = toAdd => Array.isArray(toAdd) ? getObj(toAdd) : toAdd
const DE_DUPE = (e, i, arr) => arr.indexOf(e) === i
const removeEmptyObj = (o) => {
let ignores = [null, undefined, ""],
isNonEmpty = d => !ignores.includes(d) && (typeof(d) !== "object" || Object.keys(d).length)
return JSON.parse(JSON.stringify(o), function(k, v) {
if (isNonEmpty(v))
return v;
});
}
const getDeepMerge = (ogObj, opts={})=> {
const { add, uniq=true, replace=false, removeEmpty=false } = opts
const isObject = obj => obj && typeof obj === 'object';
return [ogObj, add].reduce((prev, obj) => {
let _add = {...prev}
Object.keys(obj).forEach(key => {
const pVal = _add[key];
const oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
_add[key] = pVal.concat(...oVal).filter(uniq ? DE_DUPE : null)
if(replace){
_add[key] = oVal }
_add = removeEmpty ? removeKey(key, _add) : _add
}
else if (isObject(pVal) && isObject(oVal)) {
_add[key] = getDeepMerge(pVal, {...opts, add:oVal})
if(replace){
_add[key] = oVal
}
}
else {
_add[key] = oVal
}
});
return removeEmptyObj(_add)
}, {});
}
Live working demo: https://jsbin.com/yuduvukiqe/1/edit?js,console