mirror of
https://github.com/toss/es-toolkit.git
synced 2024-11-27 14:57:44 +03:00
feat(mergeWith): Add implementation for mergeWith
This commit is contained in:
parent
bb94b88e70
commit
cc3a467443
@ -150,6 +150,7 @@ function sidebar(): DefaultTheme.Sidebar {
|
||||
{ text: 'mapKeys', link: '/reference/object/mapKeys' },
|
||||
{ text: 'mapValues', link: '/reference/object/mapValues' },
|
||||
{ text: 'merge', link: '/reference/object/merge' },
|
||||
{ text: 'mergeWith', link: '/reference/object/mergeWith' },
|
||||
{ text: 'toMerged', link: '/reference/object/toMerged' },
|
||||
{ text: 'omit', link: '/reference/object/omit' },
|
||||
{ text: 'omitBy', link: '/reference/object/omitBy' },
|
||||
|
@ -161,6 +161,7 @@ function sidebar(): DefaultTheme.Sidebar {
|
||||
{ text: 'mapKeys', link: '/ko/reference/object/mapKeys' },
|
||||
{ text: 'mapValues', link: '/ko/reference/object/mapValues' },
|
||||
{ text: 'merge', link: '/ko/reference/object/merge' },
|
||||
{ text: 'mergeWith', link: '/ko/reference/object/mergeWith' },
|
||||
{ text: 'toMerged', link: '/ko/reference/object/toMerged' },
|
||||
{ text: 'omit', link: '/ko/reference/object/omit' },
|
||||
{ text: 'omitBy', link: '/ko/reference/object/omitBy' },
|
||||
|
@ -146,6 +146,7 @@ function sidebar(): DefaultTheme.Sidebar {
|
||||
{ text: 'mapKeys', link: '/zh_hans/reference/object/mapKeys' },
|
||||
{ text: 'mapValues', link: '/zh_hans/reference/object/mapValues' },
|
||||
{ text: 'merge', link: '/zh_hans/reference/object/merge' },
|
||||
{ text: 'mergeWith', link: '/zh_hans/reference/object/mergeWith' },
|
||||
{ text: 'toMerged', link: '/zh_hans/reference/object/toMerged' },
|
||||
{ text: 'omit', link: '/zh_hans/reference/object/omit' },
|
||||
{ text: 'omitBy', link: '/zh_hans/reference/object/omitBy' },
|
||||
|
@ -300,8 +300,8 @@ Even if a feature is marked "in review," it might already be under review to ens
|
||||
| [keysIn](https://lodash.com/docs/4.17.15#keysIn) | ❌ |
|
||||
| [mapKeys](https://lodash.com/docs/4.17.15#mapKeys) | ✅ |
|
||||
| [mapValues](https://lodash.com/docs/4.17.15#mapValues) | ✅ |
|
||||
| [merge](https://lodash.com/docs/4.17.15#merge) | ❌ |
|
||||
| [mergeWith](https://lodash.com/docs/4.17.15#mergeWith) | ❌ |
|
||||
| [merge](https://lodash.com/docs/4.17.15#merge) | ✅ |
|
||||
| [mergeWith](https://lodash.com/docs/4.17.15#mergeWith) | ✅ |
|
||||
| [omit](https://lodash.com/docs/4.17.15#omit) | 📝 |
|
||||
| [omitBy](https://lodash.com/docs/4.17.15#omitBy) | 📝 |
|
||||
| [pick](https://lodash.com/docs/4.17.15#pick) | 📝 |
|
||||
|
@ -301,8 +301,8 @@ chunk([1, 2, 3, 4], 0);
|
||||
| [keysIn](https://lodash.com/docs/4.17.15#keysIn) | ❌ |
|
||||
| [mapKeys](https://lodash.com/docs/4.17.15#mapKeys) | ✅ |
|
||||
| [mapValues](https://lodash.com/docs/4.17.15#mapValues) | ✅ |
|
||||
| [merge](https://lodash.com/docs/4.17.15#merge) | ❌ |
|
||||
| [mergeWith](https://lodash.com/docs/4.17.15#mergeWith) | ❌ |
|
||||
| [merge](https://lodash.com/docs/4.17.15#merge) | ✅ |
|
||||
| [mergeWith](https://lodash.com/docs/4.17.15#mergeWith) | ✅ |
|
||||
| [omit](https://lodash.com/docs/4.17.15#omit) | 📝 |
|
||||
| [omitBy](https://lodash.com/docs/4.17.15#omitBy) | 📝 |
|
||||
| [pick](https://lodash.com/docs/4.17.15#pick) | 📝 |
|
||||
|
83
docs/ko/reference/object/mergeWith.md
Normal file
83
docs/ko/reference/object/mergeWith.md
Normal file
@ -0,0 +1,83 @@
|
||||
# mergeWith
|
||||
|
||||
`source`가 가지고 있는 값들을 `target` 객체로 병합해요.
|
||||
|
||||
어떻게 프로퍼티를 병합하는지 지정하기 위해서 `merge` 함수 인자를 정의하세요. `merge` 함수는 병합되는 모든 프로퍼티에 대해서, 다음과 같은 인자를 가지고 호출돼요.
|
||||
|
||||
- `targetValue`: `target` 객체가 가지고 있는 값.
|
||||
- `sourceValue`: `source` 객체가 가지고 있는 값.
|
||||
- `key`: 병합되고 있는 프로퍼티 이름.
|
||||
- `target`: `target` 객체.
|
||||
- `source`: `source` 객체.
|
||||
|
||||
`merge` 함수 인자는 `target` 객체에 설정될 값을 반환해야 해요. 만약 `undefined`를 반환한다면, 기본적으로 두 값을 깊이 병합해요. 깊은 병합에서는, 중첩된 객체나 배열을 다음과 같이 재귀적으로 병합해요.
|
||||
|
||||
- `source`와 `target`의 프로퍼티가 모두 객체 또는 배열이라면, 두 객체와 배열은 병합돼요.
|
||||
- 만약에 `source`의 프로퍼티가 `undefined` 라면, `target`의 프로퍼티를 덮어씌우지 않아요.
|
||||
|
||||
이 함수는 `target` 객체를 수정해요.
|
||||
|
||||
## 인터페이스
|
||||
|
||||
```typescript
|
||||
function merge<T, S>(target: T, source: S): T & S;
|
||||
```
|
||||
|
||||
### 파라미터
|
||||
|
||||
- `target` (`T`): `source` 객체가 가지고 있는 프로퍼티를 병합할 객체. 이 객체는 함수에 의해 수정돼요.
|
||||
- `source` (`S`): `target` 객체로 프로퍼티를 병합할 객체.
|
||||
- `merge` (`(targetValue: any, sourceValue: any, key: string, target: T, source: S) => any`): 두 값을 어떻게 병합할지 정의하는 함수. 아래와 같은 인자로 호출돼요.
|
||||
- `targetValue`: `target` 객체가 가지고 있는 값.
|
||||
- `sourceValue`: `source` 객체가 가지고 있는 값.
|
||||
- `key`: 병합되고 있는 프로퍼티 이름.
|
||||
- `target`: `target` 객체.
|
||||
- `source`: `source` 객체.
|
||||
|
||||
### 반환 값
|
||||
|
||||
(`T & S`): `source` 객체가 가지고 있는 프로퍼티가 병합된 `target` 객체.
|
||||
|
||||
## 예시
|
||||
|
||||
```typescript
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: 3, c: 4 };
|
||||
|
||||
mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
return targetValue + sourceValue;
|
||||
}
|
||||
});
|
||||
// 반환 값: { a: 1, b: 5, c: 4 }
|
||||
|
||||
const target = { a: [1], b: [2] };
|
||||
const source = { a: [3], b: [4] };
|
||||
|
||||
const result = mergeWith(target, source, (objValue, srcValue) => {
|
||||
if (Array.isArray(objValue)) {
|
||||
return objValue.concat(srcValue);
|
||||
}
|
||||
});
|
||||
// 반환 값: { a: [1, 3], b: [2, 4] })
|
||||
```
|
||||
|
||||
## 데모
|
||||
|
||||
::: sandpack
|
||||
|
||||
```ts index.ts
|
||||
import { mergeWith } from 'es-toolkit';
|
||||
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: 3, c: 4 };
|
||||
|
||||
const result = mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
return targetValue + sourceValue;
|
||||
}
|
||||
});
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
:::
|
90
docs/reference/object/mergeWith.md
Normal file
90
docs/reference/object/mergeWith.md
Normal file
@ -0,0 +1,90 @@
|
||||
# mergeWith
|
||||
|
||||
Merges the properties of the source object into the target object.
|
||||
|
||||
You can provide a custom `merge` function to control how properties are merged. The `merge` function is called for each property that is being merged and receives the following arguments:
|
||||
|
||||
- `targetValue`: The current value of the property in the target object.
|
||||
- `sourceValue`: The value of the property in the source object.
|
||||
- `key`: The key of the property being merged.
|
||||
- `target`: The target object.
|
||||
- `source`: The source object.
|
||||
|
||||
The `merge` function should return the value to be set in the target object. If it returns `undefined`, a default deep merge will be applied for arrays and objects:
|
||||
|
||||
- If a property in the source object is an array or an object and the corresponding property in the target object is also an array or object, they will be merged.
|
||||
- If a property in the source object is undefined, it will not overwrite a defined property in the target object.
|
||||
|
||||
See [merge](./merge.md) for details of a default deep merge.
|
||||
|
||||
This function mutates the target object.
|
||||
|
||||
## Signature
|
||||
|
||||
```typescript
|
||||
function mergeWith<T, S>(
|
||||
target: T,
|
||||
source: S,
|
||||
merge: (targetValue: any, sourceValue: any, key: string, target: T, source: S) => any
|
||||
): T & S;
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
- `target` (`T`): The target object into which the source object properties will be merged. This object is modified in place.
|
||||
- `source` (`S`): The source object whose properties will be merged into the target object.
|
||||
- `merge` (`(targetValue: any, sourceValue: any, key: string, target: T, source: S) => any`): A custom merge function that defines how properties should be combined. It receives the following arguments:
|
||||
- `targetValue`: The current value of the property in the target object.
|
||||
- `sourceValue`: The value of the property in the source object.
|
||||
- `key`: The key of the property being merged.
|
||||
- `target`: The target object.
|
||||
- `source`: The source object.
|
||||
|
||||
### Returns
|
||||
|
||||
(`T & S`): The updated target object with properties from the source object merged in.
|
||||
|
||||
## Examples
|
||||
|
||||
```typescript
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: 3, c: 4 };
|
||||
|
||||
mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
return targetValue + sourceValue;
|
||||
}
|
||||
});
|
||||
// Returns { a: 1, b: 5, c: 4 }
|
||||
|
||||
const target = { a: [1], b: [2] };
|
||||
const source = { a: [3], b: [4] };
|
||||
|
||||
const result = mergeWith(target, source, (objValue, srcValue) => {
|
||||
if (Array.isArray(objValue)) {
|
||||
return objValue.concat(srcValue);
|
||||
}
|
||||
});
|
||||
|
||||
// Returns { a: [1, 3], b: [2, 4] })
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
::: sandpack
|
||||
|
||||
```ts index.ts
|
||||
import { mergeWith } from 'es-toolkit';
|
||||
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: 3, c: 4 };
|
||||
|
||||
const result = mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
return targetValue + sourceValue;
|
||||
}
|
||||
});
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
:::
|
@ -301,8 +301,8 @@ chunk([1, 2, 3, 4], 0);
|
||||
| [mapKeys](https://lodash.com/docs/4.17.15#mapKeys) | ✅ |
|
||||
| [mapValues](https://lodash.com/docs/4.17.15#mapValues) | ✅ |
|
||||
| [mapValues](https://lodash.com/docs/4.17.15#mapValues) | ❌ |
|
||||
| [merge](https://lodash.com/docs/4.17.15#merge) | ❌ |
|
||||
| [mergeWith](https://lodash.com/docs/4.17.15#mergeWith) | ❌ |
|
||||
| [merge](https://lodash.com/docs/4.17.15#merge) | ✅ |
|
||||
| [mergeWith](https://lodash.com/docs/4.17.15#mergeWith) | ✅ |
|
||||
| [omit](https://lodash.com/docs/4.17.15#omit) | 📝 |
|
||||
| [omitBy](https://lodash.com/docs/4.17.15#omitBy) | 📝 |
|
||||
| [pick](https://lodash.com/docs/4.17.15#pick) | 📝 |
|
||||
|
91
docs/zh_hans/reference/object/mergeWith.md
Normal file
91
docs/zh_hans/reference/object/mergeWith.md
Normal file
@ -0,0 +1,91 @@
|
||||
# mergeWith
|
||||
|
||||
将源对象的属性合并到目标对象中。
|
||||
|
||||
您可以提供自定义的 `merge` 函数来控制属性的合并方式。`merge` 函数会在每个属性被合并时调用,并接收以下参数:
|
||||
|
||||
- `targetValue`:目标对象中属性的当前值。
|
||||
- `sourceValue`:源对象中属性的值。
|
||||
- `key`:被合并的属性的键。
|
||||
- `target`:目标对象。
|
||||
- `source`:源对象。
|
||||
|
||||
`merge` 函数应返回要在目标对象中设置的值。如果返回 `undefined`,则会对数组和对象应用默认的深度合并:
|
||||
|
||||
- 如果源对象中的属性是数组或对象,而目标对象中对应的属性也是数组或对象,它们将被合并。
|
||||
- 如果源对象中的属性是 `undefined`,它不会覆盖目标对象中已定义的属性。
|
||||
|
||||
有关默认深度合并的详细信息,请参见 [merge](./merge.md)。
|
||||
|
||||
此函数会修改目标对象。
|
||||
|
||||
## 签名
|
||||
|
||||
```typescript
|
||||
function mergeWith<T, S>(
|
||||
target: T,
|
||||
source: S,
|
||||
merge: (targetValue: any, sourceValue: any, key: string, target: T, source: S) => any
|
||||
): T & S;
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
- `target` (`T`): 目标对象,源对象的属性将合并到此对象中。该对象会被就地修改。
|
||||
- `source` (`S`): 源对象,其属性将合并到目标对象中。
|
||||
- `merge` (`(targetValue: any, sourceValue: any, key: string, target: T, source: S) => any`): 自定义合并函数,用于定义属性如何组合。它接收以下参数:
|
||||
- `targetValue`: 目标对象中属性的当前值。
|
||||
- `sourceValue`: 源对象中属性的值。
|
||||
- `key`: 被合并的属性的键。
|
||||
- `target`: 目标对象。
|
||||
- `source`: 源对象。
|
||||
|
||||
### 返回值
|
||||
|
||||
(`T & S`): 合并了源对象属性的更新后的目标对象。
|
||||
|
||||
## 示例
|
||||
|
||||
```typescript
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: 3, c: 4 };
|
||||
|
||||
mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
return targetValue + sourceValue;
|
||||
}
|
||||
});
|
||||
// 返回 { a: 1, b: 5, c: 4 }
|
||||
|
||||
const target = { a: [1], b: [2] };
|
||||
const source = { a: [3], b: [4] };
|
||||
|
||||
const result = mergeWith(target, source, (objValue, srcValue) => {
|
||||
if (Array.isArray(objValue)) {
|
||||
return objValue.concat(srcValue);
|
||||
}
|
||||
});
|
||||
|
||||
// 返回 { a: [1, 3], b: [2, 4] })
|
||||
```
|
||||
|
||||
## 演示
|
||||
|
||||
::: sandpack
|
||||
|
||||
```typescript
|
||||
import { mergeWith } from 'es-toolkit';
|
||||
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: 3, c: 4 };
|
||||
|
||||
const result = mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
return targetValue + sourceValue;
|
||||
}
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
:::
|
@ -44,6 +44,7 @@ export { property } from './object/property.ts';
|
||||
export { mapKeys } from './object/mapKeys.ts';
|
||||
export { mapValues } from './object/mapValues.ts';
|
||||
export { merge } from './object/merge.ts';
|
||||
export { mergeWith } from './object/mergeWith.ts';
|
||||
|
||||
export { isPlainObject } from './predicate/isPlainObject.ts';
|
||||
export { isArray } from './predicate/isArray.ts';
|
||||
|
@ -47,8 +47,6 @@ describe('merge', () => {
|
||||
|
||||
const actual = merge(object, source);
|
||||
|
||||
console.log(actual);
|
||||
|
||||
expect(actual.bar.b).not.toBe(actual.foo.b);
|
||||
expect(actual.foo.b.c.d).toBe(actual.foo.b.c.d.foo.b.c.d);
|
||||
});
|
||||
@ -144,7 +142,7 @@ describe('merge', () => {
|
||||
expected = { 0: 1, 1: 2, 2: 3 };
|
||||
|
||||
actual = merge({}, object1);
|
||||
console.log(expected, actual.value);
|
||||
|
||||
expect(isArguments(actual.value)).toBe(false);
|
||||
expect(actual.value).toEqual(expected);
|
||||
});
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { clone } from '../../object/clone.ts';
|
||||
import { isArguments } from '../predicate/isArguments.ts';
|
||||
import { isObjectLike } from '../predicate/isObjectLike.ts';
|
||||
import { isPlainObject } from '../predicate/isPlainObject.ts';
|
||||
import { isTypedArray } from '../predicate/isTypedArray.ts';
|
||||
import { cloneDeep } from './cloneDeep.ts';
|
||||
import { noop } from '../../function/noop.ts';
|
||||
import { mergeWith } from './mergeWith.ts';
|
||||
|
||||
declare var Buffer:
|
||||
| {
|
||||
@ -237,73 +233,5 @@ export function merge<O, S1, S2, S3, S4>(
|
||||
export function merge(object: any, ...sources: any[]): any;
|
||||
|
||||
export function merge(object: any, ...sources: any[]): any {
|
||||
let result = object;
|
||||
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
const source = sources[i];
|
||||
|
||||
result = mergeDeep(object, source, new Map());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function mergeDeep(object: any, source: any, stack: Map<any, any>) {
|
||||
if (source == null || typeof source !== 'object') {
|
||||
return object;
|
||||
}
|
||||
|
||||
if (stack.has(source)) {
|
||||
return clone(stack.get(source));
|
||||
}
|
||||
|
||||
stack.set(source, object);
|
||||
|
||||
if (Array.isArray(source)) {
|
||||
source = source.slice();
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
source[i] = source[i] ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const sourceKeys = Object.keys(source);
|
||||
|
||||
for (let i = 0; i < sourceKeys.length; i++) {
|
||||
const key = sourceKeys[i];
|
||||
|
||||
let sourceValue = source[key];
|
||||
let objectValue = object[key];
|
||||
|
||||
if (isArguments(sourceValue)) {
|
||||
sourceValue = { ...sourceValue };
|
||||
}
|
||||
|
||||
if (isArguments(objectValue)) {
|
||||
objectValue = { ...objectValue };
|
||||
}
|
||||
|
||||
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(sourceValue)) {
|
||||
sourceValue = cloneDeep(sourceValue);
|
||||
}
|
||||
|
||||
if (Array.isArray(sourceValue)) {
|
||||
objectValue = typeof objectValue === 'object' ? Array.from(objectValue ?? []) : [];
|
||||
}
|
||||
|
||||
if (Array.isArray(sourceValue)) {
|
||||
object[key] = mergeDeep(objectValue, sourceValue, stack);
|
||||
} else if (isObjectLike(objectValue) && isObjectLike(sourceValue)) {
|
||||
object[key] = mergeDeep(objectValue, sourceValue, stack);
|
||||
} else if (objectValue == null && Array.isArray(sourceValue)) {
|
||||
object[key] = mergeDeep([], sourceValue, stack);
|
||||
} else if (objectValue == null && isPlainObject(sourceValue)) {
|
||||
object[key] = mergeDeep({}, sourceValue, stack);
|
||||
} else if (objectValue == null && isTypedArray(sourceValue)) {
|
||||
object[key] = cloneDeep(sourceValue);
|
||||
} else if (objectValue === undefined || sourceValue !== undefined) {
|
||||
object[key] = sourceValue;
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
return mergeWith(object, ...sources, noop);
|
||||
}
|
||||
|
59
src/compat/object/mergeWith.spec.ts
Normal file
59
src/compat/object/mergeWith.spec.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { noop } from '../../function/noop';
|
||||
import { identity } from '../_internal/identity';
|
||||
import { mergeWith } from './mergeWith';
|
||||
import { last } from '../../array/last';
|
||||
|
||||
describe('mergeWith', () => {
|
||||
it('should handle merging when `customizer` returns `undefined`', () => {
|
||||
let actual: any = mergeWith({ a: { b: [1, 1] } }, { a: { b: [0] } }, noop);
|
||||
expect(actual).toEqual({ a: { b: [0, 1] } });
|
||||
|
||||
actual = mergeWith([], [undefined], identity);
|
||||
expect(actual).toEqual([undefined]);
|
||||
});
|
||||
|
||||
it('should clone sources when `customizer` returns `undefined`', () => {
|
||||
const source1 = { a: { b: { c: 1 } } };
|
||||
const source2 = { a: { b: { d: 2 } } };
|
||||
|
||||
mergeWith({}, source1, source2, noop);
|
||||
expect(source1.a.b).toEqual({ c: 1 });
|
||||
});
|
||||
|
||||
it('should defer to `customizer` for non `undefined` results', () => {
|
||||
const actual = mergeWith({ a: { b: [0, 1] } }, { a: { b: [2] } }, (a, b) =>
|
||||
Array.isArray(a) ? a.concat(b) : undefined
|
||||
);
|
||||
|
||||
expect(actual).toEqual({ a: { b: [0, 1, 2] } });
|
||||
});
|
||||
|
||||
it('should provide `stack` to `customizer`', () => {
|
||||
let actual: any;
|
||||
|
||||
mergeWith({}, { a: { b: 2 } }, function () {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
actual = last(arguments);
|
||||
});
|
||||
|
||||
expect(actual instanceof Map).toBe(true);
|
||||
});
|
||||
|
||||
it('should overwrite primitives with source object clones', () => {
|
||||
const actual = mergeWith({ a: 0 }, { a: { b: ['c'] } }, (a, b) => (Array.isArray(a) ? a.concat(b) : undefined));
|
||||
|
||||
expect(actual).toEqual({ a: { b: ['c'] } });
|
||||
});
|
||||
|
||||
it('should pop the stack of sources for each sibling property', () => {
|
||||
const array = ['b', 'c'];
|
||||
const object = { a: ['a'] };
|
||||
const source = { a: array, b: array };
|
||||
|
||||
const actual = mergeWith(object, source, (a, b) => (Array.isArray(a) ? a.concat(b) : undefined));
|
||||
|
||||
expect(actual).toEqual({ a: ['a', 'b', 'c'], b: ['b', 'c'] });
|
||||
});
|
||||
});
|
389
src/compat/object/mergeWith.ts
Normal file
389
src/compat/object/mergeWith.ts
Normal file
@ -0,0 +1,389 @@
|
||||
import { clone } from '../../object/clone.ts';
|
||||
import { isArguments } from '../predicate/isArguments.ts';
|
||||
import { isObjectLike } from '../predicate/isObjectLike.ts';
|
||||
import { isPlainObject } from '../predicate/isPlainObject.ts';
|
||||
import { isTypedArray } from '../predicate/isTypedArray.ts';
|
||||
import { cloneDeep } from './cloneDeep.ts';
|
||||
|
||||
declare var Buffer:
|
||||
| {
|
||||
isBuffer: (a: any) => boolean;
|
||||
}
|
||||
| undefined;
|
||||
|
||||
/**
|
||||
* Merges the properties of one or more source objects into the target object.
|
||||
*
|
||||
* This function performs a deep merge, recursively merging nested objects and arrays.
|
||||
* If a property in the source object is an array or object and the corresponding property in the target object is also an array or object, they will be merged.
|
||||
* If a property in the source object is `undefined`, it will not overwrite a defined property in the target object.
|
||||
*
|
||||
* You can provide a custom `merge` function to control how properties are merged. The `merge` function is called for each property that is being merged and receives the following arguments:
|
||||
*
|
||||
* - `targetValue`: The current value of the property in the target object.
|
||||
* - `sourceValue`: The value of the property in the source object.
|
||||
* - `key`: The key of the property being merged.
|
||||
* - `target`: The target object.
|
||||
* - `source`: The source object.
|
||||
* - `stack`: A `Map` used to keep track of objects that have already been processed to handle circular references.
|
||||
*
|
||||
* The `merge` function should return the value to be set in the target object. If it returns `undefined`, a default deep merge will be applied for arrays and objects.
|
||||
*
|
||||
* The function can handle multiple source objects and will merge them all into the target object.
|
||||
*
|
||||
* @param {T} target - The target object into which the source object properties will be merged. This object is modified in place.
|
||||
* @param {S} source - The first source object whose properties will be merged into the target object.
|
||||
* @returns {T & S} The updated target object with properties from the source object(s) merged in.
|
||||
*
|
||||
* @template T - Type of the target object.
|
||||
* @template S - Type of the first source object.
|
||||
*
|
||||
* @example
|
||||
* const target = { a: 1, b: 2 };
|
||||
* const source = { b: 3, c: 4 };
|
||||
*
|
||||
* mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
* if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
* return targetValue + sourceValue;
|
||||
* }
|
||||
* });
|
||||
* // Returns { a: 1, b: 5, c: 4 }
|
||||
* @example
|
||||
* const target = { a: [1], b: [2] };
|
||||
* const source = { a: [3], b: [4] };
|
||||
*
|
||||
* const result = mergeWith(target, source, (objValue, srcValue) => {
|
||||
* if (Array.isArray(objValue)) {
|
||||
* return objValue.concat(srcValue);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* expect(result).toEqual({ a: [1, 3], b: [2, 4] });
|
||||
*/
|
||||
export function mergeWith<T, S>(
|
||||
target: T,
|
||||
source: S,
|
||||
merge: (targetValue: any, sourceValue: any, key: string, target: T, source: S, stack: Map<any, any>) => any
|
||||
): T & S;
|
||||
|
||||
/**
|
||||
* Merges the properties of one or more source objects into the target object.
|
||||
*
|
||||
* This function performs a deep merge, recursively merging nested objects and arrays.
|
||||
* If a property in the source object is an array or object and the corresponding property in the target object is also an array or object, they will be merged.
|
||||
* If a property in the source object is `undefined`, it will not overwrite a defined property in the target object.
|
||||
*
|
||||
* You can provide a custom `merge` function to control how properties are merged. The `merge` function is called for each property that is being merged and receives the following arguments:
|
||||
*
|
||||
* - `targetValue`: The current value of the property in the target object.
|
||||
* - `sourceValue`: The value of the property in the source object.
|
||||
* - `key`: The key of the property being merged.
|
||||
* - `target`: The target object.
|
||||
* - `source`: The source object.
|
||||
* - `stack`: A `Map` used to keep track of objects that have already been processed to handle circular references.
|
||||
*
|
||||
* The `merge` function should return the value to be set in the target object. If it returns `undefined`, a default deep merge will be applied for arrays and objects.
|
||||
*
|
||||
* The function can handle multiple source objects and will merge them all into the target object.
|
||||
*
|
||||
* @param {O} object - The target object into which the source object properties will be merged. This object is modified in place.
|
||||
* @param {S1} source1 - The first source object to be merged into the target object.
|
||||
* @param {S2} source2 - The second source object to be merged into the target object.
|
||||
* @returns {O & S1 & S2} The updated target object with properties from the source objects merged in.
|
||||
*
|
||||
* @template O - Type of the target object.
|
||||
* @template S1 - Type of the first source object.
|
||||
* @template S2 - Type of the second source object.
|
||||
*
|
||||
* @example
|
||||
* const target = { a: 1, b: 2 };
|
||||
* const source = { b: 3, c: 4 };
|
||||
*
|
||||
* mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
* if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
* return targetValue + sourceValue;
|
||||
* }
|
||||
* });
|
||||
* // Returns { a: 1, b: 5, c: 4 }
|
||||
* @example
|
||||
* const target = { a: [1], b: [2] };
|
||||
* const source = { a: [3], b: [4] };
|
||||
*
|
||||
* const result = mergeWith(target, source, (objValue, srcValue) => {
|
||||
* if (Array.isArray(objValue)) {
|
||||
* return objValue.concat(srcValue);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* expect(result).toEqual({ a: [1, 3], b: [2, 4] });
|
||||
*/
|
||||
export function mergeWith<O, S1, S2>(
|
||||
object: O,
|
||||
source1: S1,
|
||||
source2: S2,
|
||||
merge: (targetValue: any, sourceValue: any, key: string, target: any, source: any, stack: Map<any, any>) => any
|
||||
): O & S1 & S2;
|
||||
|
||||
/**
|
||||
* Merges the properties of one or more source objects into the target object.
|
||||
*
|
||||
* This function performs a deep merge, recursively merging nested objects and arrays.
|
||||
* If a property in the source object is an array or object and the corresponding property in the target object is also an array or object, they will be merged.
|
||||
* If a property in the source object is `undefined`, it will not overwrite a defined property in the target object.
|
||||
*
|
||||
* You can provide a custom `merge` function to control how properties are merged. The `merge` function is called for each property that is being merged and receives the following arguments:
|
||||
*
|
||||
* - `targetValue`: The current value of the property in the target object.
|
||||
* - `sourceValue`: The value of the property in the source object.
|
||||
* - `key`: The key of the property being merged.
|
||||
* - `target`: The target object.
|
||||
* - `source`: The source object.
|
||||
* - `stack`: A `Map` used to keep track of objects that have already been processed to handle circular references.
|
||||
*
|
||||
* The `merge` function should return the value to be set in the target object. If it returns `undefined`, a default deep merge will be applied for arrays and objects.
|
||||
*
|
||||
* The function can handle multiple source objects and will merge them all into the target object.
|
||||
*
|
||||
* @param {O} object - The target object into which the source object properties will be merged. This object is modified in place.
|
||||
* @param {S1} source1 - The first source object whose properties will be merged into the target object.
|
||||
* @param {S2} source2 - The second source object whose properties will be merged into the target object.
|
||||
* @param {S3} source3 - The third source object whose properties will be merged into the target object.
|
||||
* @returns {O & S1 & S2 & S3} The updated target object with properties from the source object(s) merged in.
|
||||
*
|
||||
* @template O - Type of the target object.
|
||||
* @template S1 - Type of the first source object.
|
||||
* @template S2 - Type of the second source object.
|
||||
* @template S3 - Type of the third source object.
|
||||
*
|
||||
* @example
|
||||
* const target = { a: 1, b: 2 };
|
||||
* const source = { b: 3, c: 4 };
|
||||
*
|
||||
* mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
* if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
* return targetValue + sourceValue;
|
||||
* }
|
||||
* });
|
||||
* // Returns { a: 1, b: 5, c: 4 }
|
||||
* @example
|
||||
* const target = { a: [1], b: [2] };
|
||||
* const source = { a: [3], b: [4] };
|
||||
*
|
||||
* const result = mergeWith(target, source, (objValue, srcValue) => {
|
||||
* if (Array.isArray(objValue)) {
|
||||
* return objValue.concat(srcValue);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* expect(result).toEqual({ a: [1, 3], b: [2, 4] });
|
||||
*/
|
||||
export function mergeWith<O, S1, S2, S3>(
|
||||
object: O,
|
||||
source1: S1,
|
||||
source2: S2,
|
||||
source3: S3,
|
||||
merge: (targetValue: any, sourceValue: any, key: string, target: any, source: any, stack: Map<any, any>) => any
|
||||
): O & S1 & S2 & S3;
|
||||
|
||||
/**
|
||||
* Merges the properties of one or more source objects into the target object.
|
||||
*
|
||||
* This function performs a deep merge, recursively merging nested objects and arrays.
|
||||
* If a property in the source object is an array or object and the corresponding property in the target object is also an array or object, they will be merged.
|
||||
* If a property in the source object is `undefined`, it will not overwrite a defined property in the target object.
|
||||
*
|
||||
* You can provide a custom `merge` function to control how properties are merged. The `merge` function is called for each property that is being merged and receives the following arguments:
|
||||
*
|
||||
* - `targetValue`: The current value of the property in the target object.
|
||||
* - `sourceValue`: The value of the property in the source object.
|
||||
* - `key`: The key of the property being merged.
|
||||
* - `target`: The target object.
|
||||
* - `source`: The source object.
|
||||
* - `stack`: A `Map` used to keep track of objects that have already been processed to handle circular references.
|
||||
*
|
||||
* The `merge` function should return the value to be set in the target object. If it returns `undefined`, a default deep merge will be applied for arrays and objects.
|
||||
*
|
||||
* The function can handle multiple source objects and will merge them all into the target object.
|
||||
*
|
||||
* @param {O} object - The target object into which the source object properties will be merged. This object is modified in place.
|
||||
* @param {S1} source1 - The first source object whose properties will be merged into the target object.
|
||||
* @param {S2} source2 - The second source object whose properties will be merged into the target object.
|
||||
* @param {S3} source3 - The third source object whose properties will be merged into the target object.
|
||||
* @param {S4} source4 - The fourth source object whose properties will be merged into the target object.
|
||||
* @returns {O & S1 & S2 & S3 & S4} The updated target object with properties from the source object(s) merged in.
|
||||
*
|
||||
* @template O - Type of the target object.
|
||||
* @template S1 - Type of the first source object.
|
||||
* @template S2 - Type of the second source object.
|
||||
* @template S3 - Type of the third source object.
|
||||
* @template S4 - Type of the fourth source object.
|
||||
*
|
||||
* @example
|
||||
* const target = { a: 1, b: 2 };
|
||||
* const source = { b: 3, c: 4 };
|
||||
*
|
||||
* mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
* if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
* return targetValue + sourceValue;
|
||||
* }
|
||||
* });
|
||||
* // Returns { a: 1, b: 5, c: 4 }
|
||||
* @example
|
||||
* const target = { a: [1], b: [2] };
|
||||
* const source = { a: [3], b: [4] };
|
||||
*
|
||||
* const result = mergeWith(target, source, (objValue, srcValue) => {
|
||||
* if (Array.isArray(objValue)) {
|
||||
* return objValue.concat(srcValue);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* expect(result).toEqual({ a: [1, 3], b: [2, 4] });
|
||||
*/
|
||||
export function mergeWith<O, S1, S2, S3, S4>(
|
||||
object: O,
|
||||
source1: S1,
|
||||
source2: S2,
|
||||
source3: S3,
|
||||
source4: S4,
|
||||
merge: (targetValue: any, sourceValue: any, key: string, target: any, source: any, stack: Map<any, any>) => any
|
||||
): O & S1 & S2 & S3;
|
||||
|
||||
/**
|
||||
* Merges the properties of one or more source objects into the target object.
|
||||
*
|
||||
* This function performs a deep merge, recursively merging nested objects and arrays.
|
||||
* If a property in the source object is an array or object and the corresponding property in the target object is also an array or object, they will be merged.
|
||||
* If a property in the source object is `undefined`, it will not overwrite a defined property in the target object.
|
||||
*
|
||||
* You can provide a custom `merge` function to control how properties are merged. The `merge` function is called for each property that is being merged and receives the following arguments:
|
||||
*
|
||||
* - `targetValue`: The current value of the property in the target object.
|
||||
* - `sourceValue`: The value of the property in the source object.
|
||||
* - `key`: The key of the property being merged.
|
||||
* - `target`: The target object.
|
||||
* - `source`: The source object.
|
||||
* - `stack`: A `Map` used to keep track of objects that have already been processed to handle circular references.
|
||||
*
|
||||
* The `merge` function should return the value to be set in the target object. If it returns `undefined`, a default deep merge will be applied for arrays and objects.
|
||||
*
|
||||
* The function can handle multiple source objects and will merge them all into the target object.
|
||||
*
|
||||
* @param {any} any - The target object into which the source object properties will be merged. This object is modified in place.
|
||||
* @param {any[]} sources - The source objects whose properties will be merged into the target object.
|
||||
* @returns {any} The updated target object with properties from the source object(s) merged in.
|
||||
*
|
||||
* @example
|
||||
* const target = { a: 1, b: 2 };
|
||||
* const source = { b: 3, c: 4 };
|
||||
*
|
||||
* mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
* if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
* return targetValue + sourceValue;
|
||||
* }
|
||||
* });
|
||||
* // Returns { a: 1, b: 5, c: 4 }
|
||||
* @example
|
||||
* const target = { a: [1], b: [2] };
|
||||
* const source = { a: [3], b: [4] };
|
||||
*
|
||||
* const result = mergeWith(target, source, (objValue, srcValue) => {
|
||||
* if (Array.isArray(objValue)) {
|
||||
* return objValue.concat(srcValue);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* expect(result).toEqual({ a: [1, 3], b: [2, 4] });
|
||||
*/
|
||||
export function mergeWith(object: any, ...otherArgs: any[]): any;
|
||||
|
||||
export function mergeWith(object: any, ...otherArgs: any[]): any {
|
||||
const sources = otherArgs.slice(0, -1);
|
||||
const merge = otherArgs[otherArgs.length - 1] as (
|
||||
targetValue: any,
|
||||
sourceValue: any,
|
||||
key: string,
|
||||
target: any,
|
||||
source: any,
|
||||
stack: Map<any, any>
|
||||
) => any;
|
||||
|
||||
let result = object;
|
||||
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
const source = sources[i];
|
||||
|
||||
result = mergeWithDeep(object, source, merge, new Map());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function mergeWithDeep(
|
||||
target: any,
|
||||
source: any,
|
||||
merge: (targetValue: any, sourceValue: any, key: string, target: any, source: any, stack: Map<any, any>) => any,
|
||||
stack: Map<any, any>
|
||||
) {
|
||||
if (source == null || typeof source !== 'object') {
|
||||
return target;
|
||||
}
|
||||
|
||||
if (stack.has(source)) {
|
||||
return clone(stack.get(source));
|
||||
}
|
||||
|
||||
stack.set(source, target);
|
||||
|
||||
if (Array.isArray(source)) {
|
||||
source = source.slice();
|
||||
for (let i = 0; i < source.length; i++) {
|
||||
source[i] = source[i] ?? undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const sourceKeys = Object.keys(source);
|
||||
|
||||
for (let i = 0; i < sourceKeys.length; i++) {
|
||||
const key = sourceKeys[i];
|
||||
|
||||
let sourceValue = source[key];
|
||||
let targetValue = target[key];
|
||||
|
||||
if (isArguments(sourceValue)) {
|
||||
sourceValue = { ...sourceValue };
|
||||
}
|
||||
|
||||
if (isArguments(targetValue)) {
|
||||
targetValue = { ...targetValue };
|
||||
}
|
||||
|
||||
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(sourceValue)) {
|
||||
sourceValue = cloneDeep(sourceValue);
|
||||
}
|
||||
|
||||
if (Array.isArray(sourceValue)) {
|
||||
targetValue = typeof targetValue === 'object' ? Array.from(targetValue ?? []) : [];
|
||||
}
|
||||
|
||||
const merged = merge(targetValue, sourceValue, key, target, source, stack);
|
||||
|
||||
if (merged != null) {
|
||||
target[key] = merged;
|
||||
} else if (Array.isArray(sourceValue)) {
|
||||
target[key] = mergeWithDeep(targetValue, sourceValue, merge, stack);
|
||||
} else if (isObjectLike(targetValue) && isObjectLike(sourceValue)) {
|
||||
target[key] = mergeWithDeep(targetValue, sourceValue, merge, stack);
|
||||
} else if (targetValue == null && Array.isArray(sourceValue)) {
|
||||
target[key] = mergeWithDeep([], sourceValue, merge, stack);
|
||||
} else if (targetValue == null && isPlainObject(sourceValue)) {
|
||||
target[key] = mergeWithDeep({}, sourceValue, merge, stack);
|
||||
} else if (targetValue == null && isTypedArray(sourceValue)) {
|
||||
target[key] = cloneDeep(sourceValue);
|
||||
} else if (targetValue === undefined || sourceValue !== undefined) {
|
||||
target[key] = sourceValue;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
@ -9,3 +9,4 @@ export { mapKeys } from './mapKeys.ts';
|
||||
export { mapValues } from './mapValues.ts';
|
||||
export { cloneDeep } from './cloneDeep.ts';
|
||||
export { merge } from './merge.ts';
|
||||
export { mergeWith } from './mergeWith.ts';
|
||||
|
@ -7,6 +7,8 @@ import { isObjectLike } from '../compat/predicate/isObjectLike.ts';
|
||||
* If a property in the source object is an array or an object and the corresponding property in the target object is also an array or object, they will be merged.
|
||||
* If a property in the source object is undefined, it will not overwrite a defined property in the target object.
|
||||
*
|
||||
* Note that this function mutates the target object.
|
||||
*
|
||||
* @param {T} target - The target object into which the source object properties will be merged. This object is modified in place.
|
||||
* @param {S} source - The source object whose properties will be merged into the target object.
|
||||
* @returns {T & S} The updated target object with properties from the source object merged in.
|
||||
|
52
src/object/mergeWith.spec.ts
Normal file
52
src/object/mergeWith.spec.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { mergeWith } from './mergeWith';
|
||||
|
||||
describe('mergeWith', () => {
|
||||
it('should merge properties from source object into target object using custom merge function', () => {
|
||||
const target1 = { a: 1, b: 2 };
|
||||
const source1 = { b: 3, c: 4 };
|
||||
|
||||
const result1 = mergeWith(target1, source1, (targetValue, sourceValue) => {
|
||||
if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
return targetValue + sourceValue;
|
||||
}
|
||||
});
|
||||
|
||||
expect(result1).toEqual({ a: 1, b: 5, c: 4 });
|
||||
|
||||
const target2 = { a: [1], b: [2] };
|
||||
const source2 = { a: [3], b: [4] };
|
||||
|
||||
const result2 = mergeWith(target2, source2, (objValue, srcValue) => {
|
||||
if (Array.isArray(objValue)) {
|
||||
return objValue.concat(srcValue);
|
||||
}
|
||||
});
|
||||
|
||||
expect(result2).toEqual({ a: [1, 3], b: [2, 4] });
|
||||
});
|
||||
|
||||
it('should use custom merge function for nested objects', () => {
|
||||
const target = { a: { x: 1, y: 1 }, b: 2 };
|
||||
const source = { a: { y: 2 }, b: 3 };
|
||||
|
||||
const result = mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
return targetValue + sourceValue;
|
||||
}
|
||||
});
|
||||
|
||||
expect(result).toEqual({ a: { x: 1, y: 3 }, b: 5 });
|
||||
|
||||
const target2 = { a: { c: [1] }, b: [2] };
|
||||
const source2 = { a: { c: [3] }, b: [4] };
|
||||
|
||||
const result2 = mergeWith(target2, source2, (objValue, srcValue) => {
|
||||
if (Array.isArray(objValue)) {
|
||||
return objValue.concat(srcValue);
|
||||
}
|
||||
});
|
||||
|
||||
expect(result2).toEqual({ a: { c: [1, 3] }, b: [2, 4] });
|
||||
});
|
||||
});
|
89
src/object/mergeWith.ts
Normal file
89
src/object/mergeWith.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { isObjectLike } from '../compat/predicate/isObjectLike.ts';
|
||||
|
||||
/**
|
||||
* Merges the properties of the source object into the target object.
|
||||
*
|
||||
* You can provide a custom `merge` function to control how properties are merged. The `merge` function is called for each property that is being merged and receives the following arguments:
|
||||
*
|
||||
* - `targetValue`: The current value of the property in the target object.
|
||||
* - `sourceValue`: The value of the property in the source object.
|
||||
* - `key`: The key of the property being merged.
|
||||
* - `target`: The target object.
|
||||
* - `source`: The source object.
|
||||
*
|
||||
* The `merge` function should return the value to be set in the target object. If it returns `undefined`, a default deep merge will be applied for arrays and objects:
|
||||
*
|
||||
* - If a property in the source object is an array or an object and the corresponding property in the target object is also an array or object, they will be merged.
|
||||
* - If a property in the source object is undefined, it will not overwrite a defined property in the target object.
|
||||
*
|
||||
* Note that this function mutates the target object.
|
||||
*
|
||||
* @param {T} target - The target object into which the source object properties will be merged. This object is modified in place.
|
||||
* @param {S} source - The source object whose properties will be merged into the target object.
|
||||
* @param {(targetValue: any, sourceValue: any, key: string, target: T, source: S) => any} merge - A custom merge function that defines how properties should be combined. It receives the following arguments:
|
||||
* - `targetValue`: The current value of the property in the target object.
|
||||
* - `sourceValue`: The value of the property in the source object.
|
||||
* - `key`: The key of the property being merged.
|
||||
* - `target`: The target object.
|
||||
* - `source`: The source object.
|
||||
*
|
||||
* @returns {T & S} The updated target object with properties from the source object merged in.
|
||||
*
|
||||
* @template T - Type of the target object.
|
||||
* @template S - Type of the source object.
|
||||
*
|
||||
* @example
|
||||
* const target = { a: 1, b: 2 };
|
||||
* const source = { b: 3, c: 4 };
|
||||
*
|
||||
* mergeWith(target, source, (targetValue, sourceValue) => {
|
||||
* if (typeof targetValue === 'number' && typeof sourceValue === 'number') {
|
||||
* return targetValue + sourceValue;
|
||||
* }
|
||||
* });
|
||||
* // Returns { a: 1, b: 5, c: 4 }
|
||||
* @example
|
||||
* const target = { a: [1], b: [2] };
|
||||
* const source = { a: [3], b: [4] };
|
||||
*
|
||||
* const result = mergeWith(target, source, (objValue, srcValue) => {
|
||||
* if (Array.isArray(objValue)) {
|
||||
* return objValue.concat(srcValue);
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* expect(result).toEqual({ a: [1, 3], b: [2, 4] });
|
||||
*/
|
||||
export function mergeWith<T, S>(
|
||||
target: T,
|
||||
source: S,
|
||||
merge: (targetValue: any, sourceValue: any, key: string, target: T, source: S) => any
|
||||
): T & S;
|
||||
export function mergeWith(
|
||||
target: any,
|
||||
source: any,
|
||||
merge: (targetValue: any, sourceValue: any, key: string, target: any, source: any) => any
|
||||
) {
|
||||
const sourceKeys = Object.keys(source);
|
||||
|
||||
for (let i = 0; i < sourceKeys.length; i++) {
|
||||
const key = sourceKeys[i];
|
||||
|
||||
const sourceValue = source[key];
|
||||
const targetValue = target[key];
|
||||
|
||||
const merged = merge(targetValue, sourceValue, key, target, source);
|
||||
|
||||
if (merged != null) {
|
||||
target[key] = merged;
|
||||
} else if (Array.isArray(sourceValue)) {
|
||||
target[key] = mergeWith(targetValue ?? [], sourceValue, merge);
|
||||
} else if (isObjectLike(targetValue) && isObjectLike(sourceValue)) {
|
||||
target[key] = mergeWith(targetValue ?? {}, sourceValue, merge);
|
||||
} else if (targetValue === undefined || sourceValue !== undefined) {
|
||||
target[key] = sourceValue;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
@ -10,6 +10,8 @@ import { merge } from './merge.ts';
|
||||
* - If a property in the source object is an array or object and the corresponding property in the target object is also an array or object, they will be merged.
|
||||
* - If a property in the source object is undefined, it will not overwrite a defined property in the target object.
|
||||
*
|
||||
* Note that this function does not mutate the target object.
|
||||
*
|
||||
* @param {T} target - The target object to be cloned and merged into. This object is not modified directly.
|
||||
* @param {S} source - The source object whose properties will be merged into the cloned target object.
|
||||
* @returns {T & S} A new object with properties from the source object merged into a deep clone of the target object.
|
||||
|
Loading…
Reference in New Issue
Block a user