ts-deepmerge
Version:
A TypeScript deep merge function.
70 lines (69 loc) • 2.29 kB
JavaScript
// istanbul ignore next
const isObject = (obj) => {
if (typeof obj === "object" && obj !== null) {
if (typeof Object.getPrototypeOf === "function") {
const prototype = Object.getPrototypeOf(obj);
return prototype === Object.prototype || prototype === null;
}
return Object.prototype.toString.call(obj) === "[object Object]";
}
return false;
};
const UNSAFE_KEYS = new Set([
"__proto__",
"constructor",
"prototype",
"toString",
"valueOf",
"hasOwnProperty",
"isPrototypeOf",
"propertyIsEnumerable",
"toLocaleString",
]);
export const merge = (...objects) => objects.reduce((result, current) => {
if (current === undefined) {
return result;
}
if (Array.isArray(current)) {
throw new TypeError("Arguments provided to ts-deepmerge must be objects, not arrays.");
}
Object.keys(current).forEach((key) => {
if (UNSAFE_KEYS.has(key)) {
return;
}
if (Array.isArray(result[key]) && Array.isArray(current[key])) {
result[key] = merge.options.mergeArrays
? merge.options.uniqueArrayItems
? Array.from(new Set(result[key].concat(current[key])))
: [...result[key], ...current[key]]
: current[key];
}
else if (isObject(result[key]) && isObject(current[key])) {
result[key] = merge(result[key], current[key]);
}
else if (!isObject(result[key]) && isObject(current[key])) {
result[key] = merge(current[key], undefined);
}
else {
result[key] =
current[key] === undefined
? merge.options.allowUndefinedOverrides
? current[key]
: result[key]
: current[key];
}
});
return result;
}, {});
const defaultOptions = {
allowUndefinedOverrides: true,
mergeArrays: true,
uniqueArrayItems: true,
};
merge.options = defaultOptions;
merge.withOptions = (options, ...objects) => {
merge.options = Object.assign(Object.assign({}, defaultOptions), options);
const result = merge(...objects);
merge.options = defaultOptions;
return result;
};