Skip to content

Commit

Permalink
feat(deepMergeWithArray): new function
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed May 30, 2023
1 parent 7f8b16c commit 5b2b75c
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 2 deletions.
10 changes: 9 additions & 1 deletion src/object.test.ts
Original file line number Diff line number Diff line change
@@ -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({})
Expand Down Expand Up @@ -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'] })
})
})
61 changes: 60 additions & 1 deletion src/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ export function objectEntries<T extends object>(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
*/
Expand Down Expand Up @@ -105,6 +108,62 @@ export function deepMerge<T extends object = object, S extends object = T>(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<T extends object = object, S extends object = T>(target: T, ...sources: S[]): DeepMerge<T, S> {
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)
}
Expand Down

0 comments on commit 5b2b75c

Please sign in to comment.