Here's an immutable deep version, I call it โunion that preserves formโ in TypeScript that uses lodash:
function _mergeKeepShapeArray(dest: Array<any>, source: Array<any>) { if (source.length != dest.length) { return dest; } let ret = []; dest.forEach((v, i) => { ret[i] = _mergeKeepShape(v, source[i]); }); return ret; } function _mergeKeepShapeObject(dest: Object, source: Object) { let ret = {}; Object.keys(dest).forEach((key) => { let sourceValue = source[key]; if (typeof sourceValue !== "undefined") { ret[key] = _mergeKeepShape(dest[key], sourceValue); } else { ret[key] = dest[key]; } }); return ret; } function _mergeKeepShape(dest, source) { // else if order matters here, because _.isObject is true for arrays also if (_.isArray(dest)) { if (!_.isArray(source)) { return dest; } return _mergeKeepShapeArray(dest, source); } else if (_.isObject(dest)) { if (!_.isObject(source)) { return dest; } return _mergeKeepShapeObject(dest, source); } else { return source; } } /** * Immutable merge that retains the shape of the 'existingValue' */ export const mergeKeepShape = <T>(existingValue: T, extendingValue): T => { return _mergeKeepShape(existingValue, extendingValue); }
And a simple test to see how I see such a merge:
let newObject = mergeKeepShape( { a : 5, // b is not here c : 33, d : { e : 5, // f is not here g : [1,1,1], h : [2,2,2], i : [4,4,4], } }, { a : 123, b : 444, // c is not here d : { e : 321, f : 432, // g is not here h : [3,3,3], i : [1,2], } } ); expect(newObject).toEqual({ a : 123, // b is not here c : 33, d : { e : 321, // f is not here, g : [1,1,1], h : [3,3,3], i : [4,4,4] } });
I myself used โno changeโ in the test, but did not see the need to add it to this answer.
I hereby put this in the public domain.