@ou-imdt/utils
Version:
Utility library for interactive media development
53 lines (45 loc) • 2.1 kB
JavaScript
export const skip = Symbol('skip merge value');
export function isMergeable(target) {
return typeof target === 'object' && target !== null;
}
/**
* recursively merges enumerable own properties (string/symbol keyed) from one or more source objects to a target object.
* - doesn't throw on unsupported source types (non-object)
* - creates nested objects on target where needed
* - doesn't override defined target values with undefined source values
* @param {object} target - the target object
* @param {object|function} params - source object(s), optional callback function
* @returns {object} - the modified target object
*/
export default function mergeObject(target, ...params) {
if (typeof target !== 'object' || target === null) return target;
const depth = (typeof params[params.length - 1] === 'number') ? params.pop() : 0;
const stack = (depth === 0) ? target : params.pop();
const callback = (typeof params[params.length - 1] === 'function') ? params.pop() : null;
const [source, ...sourceList] = params.filter(value => value !== null);
if (typeof source === 'object' && source !== null) {
const keys = Reflect.ownKeys(source).filter(key => Object.prototype.propertyIsEnumerable.call(source, key));
keys.forEach((key) => {
const targetValue = target[key];
const sourceValue = source[key];
if (typeof targetValue !== 'undefined' && typeof sourceValue === 'undefined') return;
const customValue = callback?.({ sourceValue, targetValue, key, target, source, stack, depth });
if (typeof customValue !== 'undefined') {
if (customValue !== skip) {
target[key] = customValue;
}
return;
}
if (isMergeable(sourceValue)) {
if (!isMergeable(target[key])) {
target[key] = Array.isArray(sourceValue) ? [] : {};
}
target[key] = mergeObject(target[key], sourceValue, callback, stack, depth + 1);
} else {
target[key] = sourceValue;
}
});
}
if (sourceList.length) mergeObject(target, ...sourceList, callback);
return target;
}