diff --git a/src/object.test.ts b/src/object.test.ts index de532fe..d8768a6 100644 --- a/src/object.test.ts +++ b/src/object.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { deepMerge, objectMap } from './object' +import { deepMerge, deepMergeWithArray, objectMap } from './object' it('objectMap', () => { expect(objectMap({}, (...args) => args)).toEqual({}) @@ -64,3 +64,11 @@ describe('deepMerge', () => { expect(obj2.polluted).toBeUndefined() }) }) + +describe('deepMergeWithArray', () => { + it('should merge array values', () => { + const obj1 = { a: ['A', 'B'] } + const obj2 = { a: ['C'], b: ['D'] } + expect(deepMergeWithArray({}, obj1, obj2)).toEqual({ a: ['A', 'B', 'C'], b: ['D'] }) + }) +}) diff --git a/src/object.ts b/src/object.ts index 089e0e1..6698247 100644 --- a/src/object.ts +++ b/src/object.ts @@ -68,7 +68,10 @@ export function objectEntries(obj: T) { } /** - * Deep merge :P + * Deep merge + * + * The first argument is the target object, the rest are the sources. + * The target object will be mutated and returned. * * @category Object */ @@ -105,6 +108,62 @@ export function deepMerge(targe return deepMerge(target, ...sources) } +/** + * Deep merge + * + * Differs from `deepMerge` in that it merges arrays instead of overriding them. + * + * The first argument is the target object, the rest are the sources. + * The target object will be mutated and returned. + * + * @category Object + */ +export function deepMergeWithArray(target: T, ...sources: S[]): DeepMerge { + if (!sources.length) + return target as any + + const source = sources.shift() + if (source === undefined) + return target as any + + if (Array.isArray(target) && Array.isArray(source)) + target.push(...source) + + if (isMergableObject(target) && isMergableObject(source)) { + objectKeys(source).forEach((key) => { + if (key === '__proto__' || key === 'constructor' || key === 'prototype') + return + + // @ts-expect-error + if (Array.isArray(source[key])) { + // @ts-expect-error + if (!target[key]) + // @ts-expect-error + target[key] = [] + + // @ts-expect-error + deepMergeWithArray(target[key], source[key]) + } + // @ts-expect-error + else if (isMergableObject(source[key])) { + // @ts-expect-error + if (!target[key]) + // @ts-expect-error + target[key] = {} + + // @ts-expect-error + deepMergeWithArray(target[key], source[key]) + } + else { + // @ts-expect-error + target[key] = source[key] + } + }) + } + + return deepMergeWithArray(target, ...sources) +} + function isMergableObject(item: any): item is Object { return isObject(item) && !Array.isArray(item) }