Created
April 2, 2021 11:51
-
-
Save sgromkov/b3d384b9a66f604ac2415100fb877500 to your computer and use it in GitHub Desktop.
Immutable deep merge. Covered by unit tests on Jest
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
/** | |
* Performs a deep merge of objects and returns a new object. | |
* Does not modify objects (immutable) and concatenates arrays. | |
* @function merge | |
* @param {...object} objects - Objects to merge | |
* @returns {object} New object with a combined key/value | |
*/ | |
const merge = function (...objects) { | |
const isObject = (obj) => obj && typeof obj === 'object'; | |
return objects.reduce((prev, obj) => { | |
const newPrev = { ...prev }; | |
Object.keys(obj).forEach((key) => { | |
const pVal = newPrev[key]; | |
const oVal = obj[key]; | |
if (Array.isArray(pVal) && Array.isArray(oVal)) { | |
newPrev[key] = pVal.concat(...oVal); | |
} else if (isObject(pVal) && isObject(oVal)) { | |
newPrev[key] = merge(pVal, oVal); | |
} else { | |
newPrev[key] = oVal; | |
} | |
}); | |
return newPrev; | |
}, {}); | |
}; | |
export default merge; |
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 merge from './merge'; | |
describe('merge performs a deep merge of objects and returns a new object', () => { | |
test('Returns an empty object', () => { | |
const x = {}; | |
const y = {}; | |
const output = {}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Returns an object without merging with a number', () => { | |
const x = 1; | |
const y = { | |
key1: 'value1', | |
key2: 'value2' | |
}; | |
const output = { | |
key1: 'value1', | |
key2: 'value2' | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Returns an empty object if numbers are passed', () => { | |
const x = 1; | |
const y = 2; | |
const output = {}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('If a string is passed creates an object from the string using indexes as keys', () => { | |
const x = 'sdf'; | |
const y = { | |
b: 2, | |
c: { y: 2, z: 2 }, | |
d: [2, 2], | |
e: 2 | |
}; | |
const output = { | |
0: 's', | |
1: 'd', | |
2: 'f', | |
b: 2, | |
c: { y: 2, z: 2 }, | |
d: [2, 2], | |
e: 2 | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Ignores NaN', () => { | |
const x = NaN; | |
const y = { | |
key1: 'value1', | |
key2: 'value2' | |
}; | |
const output = { | |
key1: 'value1', | |
key2: 'value2' | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Merges with an empty object', () => { | |
const x = { | |
key1: 'value1', | |
key2: 'value2' | |
}; | |
const y = {}; | |
const output = { | |
key1: 'value1', | |
key2: 'value2' | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Merges with an empty object if the value of one of the keys is Non', () => { | |
const x = { | |
key1: NaN | |
}; | |
const y = {}; | |
const output = { | |
key1: NaN | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Merges with a non-empty object if the value of one of the keys is Non', () => { | |
const x = { | |
key1: NaN | |
}; | |
const y = { | |
b: 2, | |
c: { y: 2, z: 2 }, | |
d: [2, 2], | |
e: 2 | |
}; | |
const output = { | |
key1: NaN, | |
b: 2, | |
c: { | |
y: 2, | |
z: 2 | |
}, | |
d: [2, 2], | |
e: 2 | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Merges by replacing the values if the key names match', () => { | |
const x = { | |
a: 'sdf', | |
b: 2 | |
}; | |
const y = { | |
a: 3, | |
c: 4 | |
}; | |
const output = { | |
a: 3, | |
b: 2, | |
c: 4 | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Merges by replacing the values of the second nesting level if the key names match', () => { | |
const x = { | |
a: { | |
x: 1, | |
y: 2 | |
}, | |
}; | |
const y = { | |
a: { | |
x: 4 | |
}, | |
}; | |
const output = { | |
a: { | |
x: 4, | |
y: 2 | |
} | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Merges not replacing, but supplementing the contents of the arrays', () => { | |
const x = { | |
a: [1, 2, 3] | |
}; | |
const y = { | |
a: [4, 5, 6] | |
}; | |
const output = { | |
a: [1, 2, 3, 4, 5, 6] | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
test('Performs merges including nesting, arrays, and keys with the same name', () => { | |
const x = { | |
foo: { bar: 3 }, | |
array: [{ | |
does: 'work', | |
too: [1, 2, 3] | |
}] | |
}; | |
const y = { | |
foo: { baz: 4 }, | |
quux: 5, | |
array: [{ | |
does: 'work', | |
too: [4, 5, 6] | |
}, { | |
really: 'yes' | |
}] | |
}; | |
const output = { | |
foo: { | |
bar: 3, | |
baz: 4 | |
}, | |
array: [{ | |
does: 'work', | |
too: [1, 2, 3] | |
}, { | |
does: 'work', | |
too: [4, 5, 6] | |
}, { | |
really: 'yes' | |
}], | |
quux: 5 | |
}; | |
expect(merge(x, y)).toEqual(output); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment