Skip to content

Instantly share code, notes, and snippets.

@sgromkov
Created April 2, 2021 11:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sgromkov/b3d384b9a66f604ac2415100fb877500 to your computer and use it in GitHub Desktop.
Save sgromkov/b3d384b9a66f604ac2415100fb877500 to your computer and use it in GitHub Desktop.
Immutable deep merge. Covered by unit tests on Jest
/**
* 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;
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