Created
March 22, 2019 15:36
-
-
Save sladg/dbe85b1794130ca9242079bc7962e355 to your computer and use it in GitHub Desktop.
Merging nested objects
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
import { assert } from 'chai' | |
import { mergeDeep as merge } from './' | |
describe('mergeDeep', () => { | |
it('should merge object properties without affecting any object', () => { | |
const obj1 = { a: 0, b: 1 } | |
const obj2 = { c: 2, d: 3 } | |
const obj3 = { a: 4, d: 5 } | |
const actual = { a: 4, b: 1, c: 2, d: 5 } | |
assert.deepEqual(merge({}, obj1, obj2, obj3), actual) | |
assert.notDeepEqual(actual, obj1) | |
assert.notDeepEqual(actual, obj2) | |
assert.notDeepEqual(actual, obj3) | |
}) | |
it('should do a deep merge', () => { | |
const obj1 = { a: { b: 1, c: 1, d: { e: 1, f: 1 } } } | |
const obj2 = { a: { b: 2, d: { f: 'f' } } } | |
const result = merge(obj1, obj2) | |
assert.deepEqual(result, { a: { b: 2, c: 1, d: { e: 1, f: 'f' } } }) | |
}) | |
it('should not merge strings', () => { | |
const obj1 = { a: 'fooo' } | |
const obj2 = { a: { b: 2, d: { f: 'f' } } } | |
const obj3 = { a: 'bar' } | |
const actual = merge(obj1, obj2, obj3) | |
assert.deepEqual(actual.a, 'bar') | |
}) | |
it('should clone objects during merge', () => { | |
const obj1 = { a: { b: 1 } } | |
const obj2 = { a: { c: 2 } } | |
const actual = merge({}, obj1, obj2) | |
assert.deepEqual(actual, { a: { b: 1, c: 2 } }) | |
assert.notDeepEqual(actual.a, obj1.a) | |
assert.notDeepEqual(actual.a, obj2.a) | |
}) | |
it('should not merge an objects into an array', () => { | |
const obj1 = { a: { b: 1 } } | |
const obj2 = { a: ['foo', 'bar'] } | |
const actual = merge({}, obj1, obj2) | |
assert.deepEqual(actual, { a: ['foo', 'bar'] }) | |
}) | |
it('should deep clone arrays during merge', () => { | |
const obj1 = { a: [1, 2, [3, 4]] } | |
const obj2 = { b: [5, 6] } | |
const actual = merge(obj1, obj2) | |
assert.deepEqual(actual.a, [1, 2, [3, 4]]) | |
assert.deepEqual(actual.a[2], [3, 4]) | |
assert.deepEqual(actual.b, obj2.b) | |
}) | |
it('should not replace actual value with empty object', () => { | |
const obj1 = { a: {} } | |
const obj2 = { a: 5 } | |
const actualLeft = merge(obj1, obj2) | |
assert.deepEqual(actualLeft, { a: 5 }) | |
}) | |
it('should replace value with undefined if set', () => { | |
const obj1 = { a: 1024 } | |
const obj2 = { a: undefined } | |
const actualLeft = merge(obj1, obj2) | |
assert.deepEqual(actualLeft, { a: undefined }) | |
const actualRight = merge(obj2, obj1) | |
assert.deepEqual(actualRight, { a: 1024 }) | |
}) | |
it('should copy source properties', () => { | |
assert.equal(merge({ test: true }).test, true) | |
}) | |
}) |
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
// tslint:disable | |
const isObject = (item) => item && typeof item === 'object' && !Array.isArray(item) | |
const mergeNew = (target, source) => { | |
// Nothing to look at, go away | |
if (target === undefined) return source | |
if (source === undefined) return target | |
// We know how to handle arrays! We don't merge those. | |
if (Array.isArray(source)) return source | |
// Nothing to merge here | |
// @TODO: we might want to merge arrays here later on | |
if (!isObject(source) || !isObject(target)) return source | |
// Replace empty object with whatever we have insted of it | |
if (!Object.entries(target || {}).length) return source | |
const entries = [...Object.entries(target || {}), ...Object.entries(source || {})] | |
const isHardToMerge = entries.reduce((acc, val) => (acc ? acc : isObject(val[1])), false) | |
// Let's do it! | |
if (!isHardToMerge) return { ...target, ...source } | |
// So it's hard, okay then | |
return entries | |
.map(([key]) => key) | |
.reduce((acc, key) => { | |
// Check if we actually want to have undefined here | |
return source.hasOwnProperty(key) && source[key] === undefined | |
? { ...acc, [key]: source[key] } | |
: { ...acc, [key]: mergeNew(target[key], source[key]) } | |
}, {}) | |
} | |
export const mergeDeep = (target, ...rest) => rest.reduce((prev, next) => mergeNew(prev, next), target) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment