diff --git a/docs/collection/merge-with.md b/docs/collection/merge-with.md index 2266481..0833aba 100644 --- a/docs/collection/merge-with.md +++ b/docs/collection/merge-with.md @@ -13,11 +13,11 @@ mergeWith({ a: [1, 2] }, { a: [3, 4] }, (objValue, srcValue) => [...objValue, .. ### Arguments -| Arg | Type | Defaults | -| -------- | --------------------------------------------------------------------------------- | -------- | -| `object` | `object` | | -| `source` | `object` | | -| `fn` | `(objValue: any, srcValue: any, key: any, object: object, source: object) => any` | | +| Arg | Type | Defaults | +| ------------ | --------------------------------------------------------------------------------- | -------- | +| `object` | `object` | | +| `...sources` | `object[]` | | +| `fn` | `(objValue: any, srcValue: any, key: any, object: object, source: object) => any` | | ### Return diff --git a/docs/collection/merge.md b/docs/collection/merge.md index c4aaf88..0259a10 100644 --- a/docs/collection/merge.md +++ b/docs/collection/merge.md @@ -13,10 +13,10 @@ merge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 }) ### Arguments -| Arg | Type | Defaults | -| -------- | -------- | -------- | -| `object` | `object` | | -| `source` | `object` | | +| Arg | Type | Defaults | +| ------------ | ---------- | -------- | +| `object` | `object` | | +| `...sources` | `object[]` | | ### Return diff --git a/docs/zh/collection/merge-with.md b/docs/zh/collection/merge-with.md index 2faf831..0623e8e 100644 --- a/docs/zh/collection/merge-with.md +++ b/docs/zh/collection/merge-with.md @@ -13,11 +13,11 @@ mergeWith({ a: [1, 2] }, { a: [3, 4] }, (objValue, srcValue) => [...objValue, .. ### 参数 -| 参数 | 类型 | 默认值 | -| -------- | --------------------------------------------------------------------------------- | ------ | -| `object` | `object` | | -| `source` | `object` | | -| `fn` | `(objValue: any, srcValue: any, key: any, object: object, source: object) => any` | | +| 参数 | 类型 | 默认值 | +| ------------ | --------------------------------------------------------------------------------- | ------ | +| `object` | `object` | | +| `...sources` | `object[]` | | +| `fn` | `(objValue: any, srcValue: any, key: any, object: object, source: object) => any` | | ### 返回值 diff --git a/docs/zh/collection/merge.md b/docs/zh/collection/merge.md index 74ce854..9709a32 100644 --- a/docs/zh/collection/merge.md +++ b/docs/zh/collection/merge.md @@ -13,10 +13,10 @@ merge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 }) ### 参数 -| 参数 | 类型 | 默认值 | -| -------- | -------- | ------ | -| `object` | `object` | | -| `source` | `object` | | +| 参数 | 类型 | 默认值 | +| ------------ | ---------- | ------ | +| `object` | `object` | | +| `...sources` | `object[]` | | ### 返回值 diff --git a/src/collection/merge.ts b/src/collection/merge.ts index b63f328..b4669ba 100644 --- a/src/collection/merge.ts +++ b/src/collection/merge.ts @@ -1,5 +1,5 @@ import { mergeWith } from './mergeWith' -export function merge, K extends Record>(object: T, source: K) { - return mergeWith(object, source, () => undefined) +export function merge, K extends Record>(object: T, ...sources: K[]) { + return mergeWith(object, ...sources, () => undefined) } diff --git a/src/collection/mergeWith.ts b/src/collection/mergeWith.ts index 5cf5541..4221a70 100644 --- a/src/collection/mergeWith.ts +++ b/src/collection/mergeWith.ts @@ -1,34 +1,55 @@ -import { hasOwn, isArray, isObject } from '../general' +import { at } from '../array' +import { hasOwn, isArray, isFunction, isObject } from '../general' + +type Fn = (objValue: any, srcValue: any, key: string | number | symbol, object?: any, source?: any) => any export function mergeWith, K extends Record>( object: T, - source: K, - fn: (objValue: any, srcValue: any, key: string | number | symbol, object?: T, source?: K) => any, + ...sources: [...K[], fn: Fn] ): T & K { - function baseMerge(target: any, src: any): any { - // eslint-disable-next-line no-restricted-syntax - for (const key in src) { - if (hasOwn(src, key)) { - const srcValue = src[key] - const targetValue = target[key] - - const customResult = fn(targetValue, srcValue, key, object, source) - - if (customResult !== undefined) { - target[key] = customResult - } else if (isObject(srcValue)) { - if (isObject(targetValue)) { - target[key] = baseMerge(targetValue, srcValue) + const fn = at(sources, -1) as Fn + const targets = [object, ...sources.slice(0, -1)] as (T & K)[] + + let len = targets.length - 1 + let result = targets[len] + + while (len) { + result = baseMergeWith(targets[len - 1], result, fn) + len-- + } + + function baseMergeWith, K extends Record>( + object: T, + source: K, + fn: (objValue: any, srcValue: any, key: string | number | symbol, object?: T, source?: K) => any, + ): T & K { + function baseMerge(target: any, src: any): any { + // eslint-disable-next-line no-restricted-syntax + for (const key in src) { + if (hasOwn(src, key)) { + const srcValue = src[key] + const targetValue = target[key] + + const customResult = fn(targetValue, srcValue, key, object, source) + + if (customResult !== undefined) { + target[key] = customResult + } else if (isObject(srcValue)) { + if (isObject(targetValue)) { + target[key] = baseMerge(targetValue, srcValue) + } else { + target[key] = baseMerge(isArray(srcValue) ? [] : {}, srcValue) + } } else { - target[key] = baseMerge(isArray(srcValue) ? [] : {}, srcValue) + target[key] = srcValue } - } else { - target[key] = srcValue } } + return target } - return target + + return baseMerge(object as any, source as any) as T & K } - return baseMerge(object as any, source as any) as T & K + return result } diff --git a/tests/collection.spec.ts b/tests/collection.spec.ts index 0f5c15e..c2b7af0 100644 --- a/tests/collection.spec.ts +++ b/tests/collection.spec.ts @@ -1,17 +1,23 @@ import { describe, it, expect } from 'vitest' import { merge, mergeWith, cloneDeep, cloneDeepWith, isNumber, hasOwn } from '../src' +it('should merge self', () => { + const obj = { a: 1 } + expect(merge(obj)).toBe(obj) +}) + it('should merge two objects', () => { - const result = merge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 }) - expect(result).toEqual({ a: 1, b: { c: 2, d: 3 }, e: 4 }) + expect(merge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })).toEqual({ a: 1, b: { c: 2, d: 3 }, e: 4 }) }) -it('should handle nested merges', () => { - const result = merge({ a: { b: 1 } }, { a: { c: 2 } }) - expect(result).toEqual({ a: { b: 1, c: 2 } }) +it('should merge rest objects', () => { + expect(merge({ a: 1 }, { b: 2 }, { c: 3 }, { a: 4 })).toEqual({ a: 4, b: 2, c: 3 }) +}) - const result2 = merge({ a: 1 }, { b: { c: 1 } }) - expect(result2).toEqual({ a: 1, b: { c: 1 } }) +it('should handle nested merges', () => { + expect(merge({ a: { b: 1 } }, { a: { c: 2 } })).toEqual({ a: { b: 1, c: 2 } }) + expect(merge({ a: 1 }, { b: { c: 1 } })).toEqual({ a: 1, b: { c: 1 } }) + expect(merge({ a: 1 }, { b: { c: 1 } }, { a: 4, b: { d: 1 } })).toEqual({ a: 4, b: { c: 1, d: 1 } }) }) it('should create new properties if they don’t exist in the target', () => { @@ -25,8 +31,20 @@ it('should handle array merging correctly', () => { }) it('should use callback for array merging', () => { - const result = mergeWith({ a: [1, 2] }, { a: [3, 4] }, (objValue, srcValue) => [...objValue, ...srcValue]) - expect(result).toEqual({ a: [1, 2, 3, 4] }) + expect(mergeWith({ a: [1, 2] }, { a: [3, 4] }, (objValue, srcValue) => [...objValue, ...srcValue])).toEqual({ + a: [1, 2, 3, 4], + }) + + expect( + mergeWith({ a: [1, 2] }, { a: [3, 4] }, { b: [5, 6] }, { b: [7, 8], d: [9, 10] }, (objValue, srcValue) => [ + ...(objValue ?? []), + ...(srcValue ?? []), + ]), + ).toEqual({ + a: [1, 2, 3, 4], + b: [5, 6, 7, 8], + d: [9, 10], + }) }) it('should use the callback to override values', () => {