@handy-common-utils/misc-utils
Version:
Miscellaneous utilities
109 lines • 4.34 kB
JavaScript
;
/* eslint-disable unicorn/no-array-callback-reference */
/* eslint-disable unicorn/prefer-spread */
Object.defineProperty(exports, "__esModule", { value: true });
exports.merge = merge;
const isObject = (value) => {
const type = typeof value;
return value != null && (type === 'object' || type === 'function') && !Array.isArray(value);
};
const isPlainObject = (value) => {
if (!isObject(value) || Object.prototype.toString.call(value) !== '[object Object]') {
return false;
}
const proto = Object.getPrototypeOf(value);
if (proto === null) {
return true;
}
const Ctor = Object.prototype.hasOwnProperty.call(proto, 'constructor') && proto.constructor;
return typeof Ctor === 'function' && Ctor instanceof Ctor && Function.prototype.toString.call(Ctor) === Function.prototype.toString.call(Object);
};
/**
* Recursively merges properties of one or more source objects into a destination object.
*
* @param options - Customizes the merge behavior.
* @param destination - The object to merge properties into. It will be mutated unless `options.immutable` is true.
* @param sources - The source objects.
* @returns The merged object.
*/
function merge(options, destination, ...sources) {
const finalOptions = Object.assign({ immutable: false, array: 'merge', set: 'replace' }, options);
const deepClone = (value) => {
if (!isObject(value)) {
return value;
}
if (value instanceof Set) {
return new Set(Array.from(value.values()).map(deepClone));
}
if (Array.isArray(value)) {
return value.map(deepClone);
}
if (isPlainObject(value)) {
const newObj = {};
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
newObj[key] = deepClone(value[key]);
}
}
return newObj;
}
// For other complex objects (like Date, RegExp, class instances), return them as is.
return value;
};
let output = finalOptions.immutable ? deepClone(destination) : destination;
const baseMerge = (target, source) => {
if (!isObject(target) || !isObject(source)) {
return;
}
Object.keys(source).forEach(key => {
const targetValue = target[key];
const sourceValue = source[key];
if (sourceValue === undefined) {
return;
}
// Handle Sets
if (targetValue instanceof Set && sourceValue instanceof Set) {
// eslint-disable-next-line perfectionist/sort-sets
target[key] = finalOptions.set === 'merge' ? new Set([...targetValue, ...sourceValue]) : sourceValue;
return;
}
// Handle Arrays
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
if (finalOptions.array === 'replace') {
target[key] = sourceValue;
}
else if (finalOptions.array === 'append') {
target[key] = targetValue.concat(sourceValue);
}
else { // 'merge'
sourceValue.forEach((item, index) => {
if (isPlainObject(targetValue[index]) && isPlainObject(item)) {
baseMerge(targetValue[index], item);
}
else if (index < targetValue.length) {
targetValue[index] = item;
}
else {
targetValue.push(item);
}
});
}
return;
}
// Handle plain objects
if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
baseMerge(targetValue, sourceValue);
return;
}
// For all other cases (primitives, non-plain objects, etc.), replace the value.
target[key] = sourceValue;
});
};
for (const source of sources) {
if (source) {
baseMerge(output, source);
}
}
return output;
}
//# sourceMappingURL=merge.js.map