UNPKG

@tienedev/datype

Version:

Modern TypeScript utility library with pragmatic typing and zero dependencies

80 lines (78 loc) 2.67 kB
function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) { return false; } if (Array.isArray(obj)) { return false; } if (obj instanceof Date || obj instanceof RegExp) { return false; } const proto = Object.getPrototypeOf(obj); return proto === null || proto === Object.prototype; } const DEFAULT_OPTIONS = { arrayMergeStrategy: 'concat', maxDepth: 50, }; function mergeInternal(target, sources, options, depth = 0, seen = new WeakSet()) { if (depth > options.maxDepth) { throw new Error(`Maximum merge depth (${options.maxDepth}) exceeded`); } if (seen.has(target)) { return target; } seen.add(target); const result = { ...target }; for (const source of sources) { if (!isPlainObject(source)) { continue; } for (const key of Object.keys(source)) { const sourceValue = source[key]; const targetValue = result[key]; if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { // eslint-disable-next-line @typescript-eslint/no-explicit-any result[key] = options.arrayMergeStrategy === 'concat' ? [...targetValue, ...sourceValue] : sourceValue; } else if (isPlainObject(targetValue) && isPlainObject(sourceValue)) { // eslint-disable-next-line @typescript-eslint/no-explicit-any result[key] = mergeInternal(targetValue, [sourceValue], options, depth + 1, seen); } else { // eslint-disable-next-line @typescript-eslint/no-explicit-any result[key] = sourceValue; } } } seen.delete(target); return result; } function deepMerge(target, ...sourcesAndOptions // eslint-disable-next-line @typescript-eslint/no-explicit-any ) { if (!isPlainObject(target)) { throw new TypeError('Target must be a plain object'); } if (sourcesAndOptions.length === 0) { return { ...target }; } let options = {}; let sources = []; const lastArg = sourcesAndOptions[sourcesAndOptions.length - 1]; if (lastArg && typeof lastArg === 'object' && ('arrayMergeStrategy' in lastArg || 'maxDepth' in lastArg)) { options = lastArg; sources = sourcesAndOptions.slice(0, -1); } else { sources = sourcesAndOptions; } const mergedOptions = { ...DEFAULT_OPTIONS, ...options }; return mergeInternal(target, sources, mergedOptions); } export { deepMerge };