mirror of
https://github.com/toss/es-toolkit.git
synced 2024-11-27 14:57:44 +03:00
feat(merge, toMerged): Add merge & toMerged
This commit is contained in:
parent
bff10652b4
commit
0054dc8119
19
benchmarks/bundle-size/merge.spec.ts
Normal file
19
benchmarks/bundle-size/merge.spec.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { getBundleSize } from './utils/getBundleSize';
|
||||
|
||||
describe('camelCase bundle size', () => {
|
||||
it('lodash-es', async () => {
|
||||
const bundleSize = await getBundleSize('lodash-es', 'camelCase');
|
||||
expect(bundleSize).toMatchInlineSnapshot(`7293`);
|
||||
});
|
||||
|
||||
it('es-toolkit', async () => {
|
||||
const bundleSize = await getBundleSize('es-toolkit', 'merge');
|
||||
expect(bundleSize).toMatchInlineSnapshot(`252`);
|
||||
});
|
||||
|
||||
it('es-toolkit/compat', async () => {
|
||||
const bundleSize = await getBundleSize('es-toolkit/compat', 'merge');
|
||||
expect(bundleSize).toMatchInlineSnapshot(`4256`);
|
||||
});
|
||||
});
|
26
benchmarks/performance/merge.bench.ts
Normal file
26
benchmarks/performance/merge.bench.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { merge as mergeToolkit } from 'es-toolkit';
|
||||
import { merge as mergeCompatToolkit } from 'es-toolkit/compat';
|
||||
import { merge as mergeLodash } from 'lodash';
|
||||
|
||||
const object = {
|
||||
a: [{ b: 2 }, { d: 4 }],
|
||||
};
|
||||
|
||||
const other = {
|
||||
a: [{ c: 3 }, { e: 5 }],
|
||||
};
|
||||
|
||||
describe('merge', () => {
|
||||
bench('lodash/merge', () => {
|
||||
mergeLodash(object, other);
|
||||
});
|
||||
|
||||
bench('es-toolkit/merge', () => {
|
||||
mergeToolkit(object, other);
|
||||
});
|
||||
|
||||
bench('es-toolkit/compat/merge', () => {
|
||||
mergeCompatToolkit(object, other);
|
||||
});
|
||||
});
|
@ -149,6 +149,8 @@ function sidebar(): DefaultTheme.Sidebar {
|
||||
{ text: 'flattenObject', link: '/reference/object/flattenObject' },
|
||||
{ text: 'mapKeys', link: '/reference/object/mapKeys' },
|
||||
{ text: 'mapValues', link: '/reference/object/mapValues' },
|
||||
{ text: 'merge', link: '/reference/object/merge' },
|
||||
{ text: 'toMerged', link: '/reference/object/toMerged' },
|
||||
{ text: 'omit', link: '/reference/object/omit' },
|
||||
{ text: 'omitBy', link: '/reference/object/omitBy' },
|
||||
{ text: 'pick', link: '/reference/object/pick' },
|
||||
|
@ -160,6 +160,8 @@ function sidebar(): DefaultTheme.Sidebar {
|
||||
{ text: 'flattenObject', link: '/ko/reference/object/flattenObject' },
|
||||
{ text: 'mapKeys', link: '/ko/reference/object/mapKeys' },
|
||||
{ text: 'mapValues', link: '/ko/reference/object/mapValues' },
|
||||
{ text: 'merge', link: '/ko/reference/object/merge' },
|
||||
{ text: 'toMerged', link: '/ko/reference/object/toMerged' },
|
||||
{ text: 'omit', link: '/ko/reference/object/omit' },
|
||||
{ text: 'omitBy', link: '/ko/reference/object/omitBy' },
|
||||
{ text: 'pick', link: '/ko/reference/object/pick' },
|
||||
|
@ -145,6 +145,8 @@ function sidebar(): DefaultTheme.Sidebar {
|
||||
{ text: 'flattenObject', link: '/zh_hans/reference/object/flattenObject' },
|
||||
{ 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: 'toMerged', link: '/zh_hans/reference/object/toMerged' },
|
||||
{ text: 'omit', link: '/zh_hans/reference/object/omit' },
|
||||
{ text: 'omitBy', link: '/zh_hans/reference/object/omitBy' },
|
||||
{ text: 'pick', link: '/zh_hans/reference/object/pick' },
|
||||
|
62
docs/ko/reference/object/merge.md
Normal file
62
docs/ko/reference/object/merge.md
Normal file
@ -0,0 +1,62 @@
|
||||
# merge
|
||||
|
||||
`source`가 가지고 있는 값들을 `target` 객체로 병합해요.
|
||||
|
||||
이 함수는 깊은 병합을 수행하는데요, 중첩된 객체나 배열도 재귀적으로 병합돼요.
|
||||
|
||||
- `source`와 `target`의 프로퍼티가 모두 객체 또는 배열이라면, 두 객체와 배열은 병합돼요.
|
||||
- 만약에 `source`의 프로퍼티가 `undefined` 라면, `target`의 프로퍼티를 덮어씌우지 않아요.
|
||||
|
||||
[toMerged](./toMerged.md)와 다르게, 이 함수는 `target` 객체를 수정해요.
|
||||
|
||||
## 인터페이스
|
||||
|
||||
```typescript
|
||||
function merge<T, S>(target: T, source: S): T & S;
|
||||
```
|
||||
|
||||
### 파라미터
|
||||
|
||||
- `target` (`T`): `source` 객체가 가지고 있는 프로퍼티를 병합할 객체. 이 객체는 함수에 의해 수정돼요.
|
||||
- `source` (`S`): `target` 객체로 프로퍼티를 병합할 객체.
|
||||
|
||||
### 반환 값
|
||||
|
||||
(`T & S`): `source` 객체가 가지고 있는 프로퍼티가 병합된 `target` 객체.
|
||||
|
||||
## 예시
|
||||
|
||||
```typescript
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
// 반환 값: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
|
||||
const target = { a: [1, 2], b: { x: 1 } };
|
||||
const source = { a: [3], b: { y: 2 } };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
// 반환 값: { a: [3, 2], b: { x: 1, y: 2 } }
|
||||
|
||||
const target = { a: null };
|
||||
const source = { a: [1, 2, 3] };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
// 반환 값: { a: [1, 2, 3] }
|
||||
```
|
||||
|
||||
## 데모
|
||||
|
||||
::: sandpack
|
||||
|
||||
```ts index.ts
|
||||
import { merge } from 'es-toolkit';
|
||||
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
:::
|
62
docs/ko/reference/object/toMerged.md
Normal file
62
docs/ko/reference/object/toMerged.md
Normal file
@ -0,0 +1,62 @@
|
||||
# toMerged
|
||||
|
||||
`source`가 가지고 있는 값들을 `target` 객체로 병합해요.
|
||||
|
||||
이 함수는 깊은 병합을 수행하는데요, 중첩된 객체나 배열도 재귀적으로 병합돼요.
|
||||
|
||||
- `source`와 `target`의 프로퍼티가 모두 객체 또는 배열이라면, 두 객체와 배열은 병합돼요.
|
||||
- 만약에 `source`의 프로퍼티가 `undefined` 라면, `target`의 프로퍼티를 덮어씌우지 않아요.
|
||||
|
||||
[merge](./merge.md)와 다르게, 이 함수는 `target` 객체를 수정하지 않아요.
|
||||
|
||||
## 인터페이스
|
||||
|
||||
```typescript
|
||||
function toMerged<T, S>(target: T, source: S): T & S;
|
||||
```
|
||||
|
||||
### 파라미터
|
||||
|
||||
- `target` (`T`): `source` 객체가 가지고 있는 프로퍼티를 병합할 객체.
|
||||
- `source` (`S`): `target` 객체로 프로퍼티를 병합할 객체.
|
||||
|
||||
### 반환 값
|
||||
|
||||
(`T & S`): `source`와 `target` 객체가 가지고 있는 프로퍼티가 병합된 새로운 객체
|
||||
|
||||
## 예시
|
||||
|
||||
```typescript
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
// 반환 값: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
|
||||
const target = { a: [1, 2], b: { x: 1 } };
|
||||
const source = { a: [3], b: { y: 2 } };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
// 반환 값: { a: [3, 2], b: { x: 1, y: 2 } }
|
||||
|
||||
const target = { a: null };
|
||||
const source = { a: [1, 2, 3] };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
// 반환 값: { a: [1, 2, 3] }
|
||||
```
|
||||
|
||||
## 데모
|
||||
|
||||
::: sandpack
|
||||
|
||||
```ts index.ts
|
||||
import { toMerged } from 'es-toolkit';
|
||||
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
:::
|
62
docs/reference/object/merge.md
Normal file
62
docs/reference/object/merge.md
Normal file
@ -0,0 +1,62 @@
|
||||
# merge
|
||||
|
||||
Merges the properties of the source object into the target object.
|
||||
|
||||
This function performs a deep merge, meaning nested objects and arrays are merged recursively.
|
||||
|
||||
- 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.
|
||||
|
||||
Unlike [toMerged](./toMerged.md), this function mutates the target object.
|
||||
|
||||
## Signature
|
||||
|
||||
```typescript
|
||||
function merge<T, S>(target: T, source: S): 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.
|
||||
|
||||
### Returns
|
||||
|
||||
(`T & S`): The updated target object with properties from the source object merged in.
|
||||
|
||||
## Examples
|
||||
|
||||
```typescript
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
// Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
|
||||
const target = { a: [1, 2], b: { x: 1 } };
|
||||
const source = { a: [3], b: { y: 2 } };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
// Output: { a: [3, 2], b: { x: 1, y: 2 } }
|
||||
|
||||
const target = { a: null };
|
||||
const source = { a: [1, 2, 3] };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
// Output: { a: [1, 2, 3] }
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
::: sandpack
|
||||
|
||||
```ts index.ts
|
||||
import { merge } from 'es-toolkit';
|
||||
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
:::
|
62
docs/reference/object/toMerged.md
Normal file
62
docs/reference/object/toMerged.md
Normal file
@ -0,0 +1,62 @@
|
||||
# toMerged
|
||||
|
||||
Merges the properties of the source object into the target object.
|
||||
|
||||
This function performs a deep merge, meaning nested objects and arrays are merged recursively.
|
||||
|
||||
- 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.
|
||||
|
||||
Unlike [merge](./merge.md), this function does not mutate the target object.
|
||||
|
||||
## Signature
|
||||
|
||||
```typescript
|
||||
function toMerged<T, S>(target: T, source: S): T & S;
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
- `target` (`T`): The target object into which the source object properties will be merged.
|
||||
- `source` (`S`): 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.
|
||||
|
||||
## Examples
|
||||
|
||||
```typescript
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
// Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
|
||||
const target = { a: [1, 2], b: { x: 1 } };
|
||||
const source = { a: [3], b: { y: 2 } };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
// Output: { a: [3, 2], b: { x: 1, y: 2 } }
|
||||
|
||||
const target = { a: null };
|
||||
const source = { a: [1, 2, 3] };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
// Output: { a: [1, 2, 3] }
|
||||
```
|
||||
|
||||
## Demo
|
||||
|
||||
::: sandpack
|
||||
|
||||
```ts index.ts
|
||||
import { toMerged } from 'es-toolkit';
|
||||
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
:::
|
62
docs/zh_hans/reference/object/merge.md
Normal file
62
docs/zh_hans/reference/object/merge.md
Normal file
@ -0,0 +1,62 @@
|
||||
# merge
|
||||
|
||||
将源对象的属性合并到目标对象中。
|
||||
|
||||
此函数执行深度合并,意味着嵌套的对象和数组会递归地合并。
|
||||
|
||||
- 如果源对象中的属性是数组或对象,而目标对象中的相应属性也是数组或对象,它们将被合并。
|
||||
- 如果源对象中的属性是未定义的,它不会覆盖目标对象中已定义的属性。
|
||||
|
||||
与 [toMerged](./toMerged.md) 不同,此函数会修改目标对象。
|
||||
|
||||
## 签名
|
||||
|
||||
```typescript
|
||||
function merge<T, S>(target: T, source: S): T & S;
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
- `target` (`T`): 目标对象,源对象的属性将被合并到这个对象中。这个对象会被原地修改。
|
||||
- `source` (`S`): 源对象,其属性将被合并到目标对象中。
|
||||
|
||||
### 返回值
|
||||
|
||||
(`T & S`): 更新后的目标对象,其中包含了源对象的合并属性。
|
||||
|
||||
## 示例
|
||||
|
||||
```typescript
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
// 输出: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
|
||||
const target = { a: [1, 2], b: { x: 1 } };
|
||||
const source = { a: [3], b: { y: 2 } };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
// 输出: { a: [3, 2], b: { x: 1, y: 2 } }
|
||||
|
||||
const target = { a: null };
|
||||
const source = { a: [1, 2, 3] };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
// 输出: { a: [1, 2, 3] }
|
||||
```
|
||||
|
||||
## 演示
|
||||
|
||||
::: sandpack
|
||||
|
||||
```ts index.ts
|
||||
import { merge } from 'es-toolkit';
|
||||
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = merge(target, source);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
:::
|
62
docs/zh_hans/reference/object/toMerged.md
Normal file
62
docs/zh_hans/reference/object/toMerged.md
Normal file
@ -0,0 +1,62 @@
|
||||
# toMerged
|
||||
|
||||
将源对象的属性合并到目标对象中。
|
||||
|
||||
此函数执行深度合并,意味着嵌套的对象和数组会递归地合并。
|
||||
|
||||
- 如果源对象中的属性是数组或对象,而目标对象中的相应属性也是数组或对象,它们将被合并。
|
||||
- 如果源对象中的属性是未定义的,它不会覆盖目标对象中已定义的属性。
|
||||
|
||||
与 [merge](./merge.md) 不同,这个函数不会修改目标对象。
|
||||
|
||||
## 签名
|
||||
|
||||
```typescript
|
||||
function toMerged<T, S>(target: T, source: S): T & S;
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
- `target` (`T`): 目标对象,源对象的属性将被合并到这个对象中。
|
||||
- `source` (`S`): 源对象,其属性将被合并到目标对象中。
|
||||
|
||||
### 返回值
|
||||
|
||||
(`T & S`): 更新后的目标对象,其中包含了源对象的合并属性。
|
||||
|
||||
## 示例
|
||||
|
||||
```typescript
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
// 输出: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
|
||||
const target = { a: [1, 2], b: { x: 1 } };
|
||||
const source = { a: [3], b: { y: 2 } };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
// 输出: { a: [3, 2], b: { x: 1, y: 2 } }
|
||||
|
||||
const target = { a: null };
|
||||
const source = { a: [1, 2, 3] };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
// 输出: { a: [1, 2, 3] }
|
||||
```
|
||||
|
||||
## 演示
|
||||
|
||||
::: sandpack
|
||||
|
||||
```ts index.ts
|
||||
import { toMerged } from 'es-toolkit';
|
||||
|
||||
const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
const result = toMerged(target, source);
|
||||
console.log(result);
|
||||
```
|
||||
|
||||
:::
|
@ -43,6 +43,7 @@ export { set } from './object/set.ts';
|
||||
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 { isPlainObject } from './predicate/isPlainObject.ts';
|
||||
export { isArray } from './predicate/isArray.ts';
|
||||
|
354
src/compat/object/merge.spec.ts
Normal file
354
src/compat/object/merge.spec.ts
Normal file
@ -0,0 +1,354 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { merge } from './merge';
|
||||
import { args } from '../_internal/args';
|
||||
import { isArguments } from '../predicate/isArguments';
|
||||
import { typedArrays } from '../_internal/typedArrays';
|
||||
import { range } from '../../math/range';
|
||||
import { stubTrue } from '../_internal/stubTrue';
|
||||
import { isEqual } from '../../predicate/isEqual';
|
||||
|
||||
describe('merge', () => {
|
||||
it('should merge `source` into `object`', () => {
|
||||
const names = {
|
||||
characters: [{ name: 'barney' }, { name: 'fred' }],
|
||||
};
|
||||
|
||||
const ages = {
|
||||
characters: [{ age: 36 }, { age: 40 }],
|
||||
};
|
||||
|
||||
const heights = {
|
||||
characters: [{ height: '5\'4"' }, { height: '5\'5"' }],
|
||||
};
|
||||
|
||||
const expected = {
|
||||
characters: [
|
||||
{ name: 'barney', age: 36, height: '5\'4"' },
|
||||
{ name: 'fred', age: 40, height: '5\'5"' },
|
||||
],
|
||||
};
|
||||
|
||||
expect(merge(names, ages, heights)).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should merge sources containing circular references', () => {
|
||||
const object: any = {
|
||||
foo: { a: 1 },
|
||||
bar: { a: 2 },
|
||||
};
|
||||
|
||||
const source: any = {
|
||||
foo: { b: { c: { d: {} } } },
|
||||
bar: {},
|
||||
};
|
||||
|
||||
source.foo.b.c.d = source;
|
||||
source.bar.b = source.foo.b;
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('should work with four arguments', () => {
|
||||
const expected = { a: 4 };
|
||||
const actual = merge({ a: 1 }, { a: 2 }, { a: 3 }, expected);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should merge onto function `object` values', () => {
|
||||
function Foo() {}
|
||||
|
||||
const source = { a: 1 };
|
||||
const actual = merge(Foo, source);
|
||||
|
||||
expect(actual).toBe(Foo);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(Foo.a).toBe(1);
|
||||
});
|
||||
|
||||
it('should treat sparse array sources as dense', () => {
|
||||
var array = [1];
|
||||
array[2] = 3;
|
||||
|
||||
var actual = merge([], array),
|
||||
expected: any = array.slice();
|
||||
|
||||
expected[1] = undefined;
|
||||
|
||||
expect('1' in actual).toBe(true);
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should merge first source object properties to function', () => {
|
||||
const fn = function () {};
|
||||
const object = { prop: {} };
|
||||
const actual = merge({ prop: fn }, object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
});
|
||||
|
||||
it('should merge first and second source object properties to function', () => {
|
||||
const fn = function () {};
|
||||
const object = { prop: {} };
|
||||
const actual = merge({ prop: fn }, { prop: fn }, object);
|
||||
|
||||
expect(actual).toEqual(object);
|
||||
});
|
||||
|
||||
it('should not merge onto function values of sources', () => {
|
||||
const source1 = { a: function () {} };
|
||||
const source2 = { a: { b: 2 } };
|
||||
const expected = { a: { b: 2 } };
|
||||
let actual = merge({}, source1, source2);
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
expect('b' in source1.a).toBe(false);
|
||||
|
||||
actual = merge(source1, source2);
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should merge onto non-plain `object` values', () => {
|
||||
function Foo() {}
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const object = new Foo();
|
||||
const actual = merge(object, { a: 1 });
|
||||
|
||||
expect(actual).toBe(object);
|
||||
expect(object.a).toBe(1);
|
||||
});
|
||||
|
||||
it('should merge `arguments` objects', () => {
|
||||
const object1 = { value: args };
|
||||
const object2 = { value: { 3: 4 } };
|
||||
let expected: any = { 0: 1, 1: 2, 2: 3, 3: 4 };
|
||||
let actual: any = merge(object1, object2);
|
||||
|
||||
expect('3' in args).toBe(false);
|
||||
expect(isArguments(actual.value)).toBe(false);
|
||||
expect(actual.value).toEqual(expected);
|
||||
object1.value = args;
|
||||
|
||||
actual = merge(object2, object1);
|
||||
expect(isArguments(actual.value)).toBe(false);
|
||||
expect(actual.value).toEqual(expected);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
it('should merge typed arrays', () => {
|
||||
const array1 = [0];
|
||||
const array2 = [0, 0];
|
||||
const array3 = [0, 0, 0, 0];
|
||||
const array4 = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
const arrays = [array2, array1, array4, array3, array2, array4, array4, array3, array2];
|
||||
const buffer = new ArrayBuffer(8);
|
||||
|
||||
let expected = typedArrays.map((type, index) => {
|
||||
const array = arrays[index].slice();
|
||||
array[0] = 1;
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
return globalThis[type] ? { value: array } : false;
|
||||
});
|
||||
|
||||
let actual = typedArrays.map(type => {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const Ctor = globalThis[type];
|
||||
return Ctor ? merge({ value: new Ctor(buffer) }, { value: [1] }) : false;
|
||||
});
|
||||
|
||||
expect(Array.isArray(actual)).toBe(true);
|
||||
expect(actual).toEqual(expected);
|
||||
|
||||
expected = typedArrays.map((type, index) => {
|
||||
const array = arrays[index].slice();
|
||||
array.push(1);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
return globalThis[type] ? { value: array } : false;
|
||||
});
|
||||
|
||||
actual = typedArrays.map((type, index) => {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const Ctor = globalThis[type];
|
||||
const array = range(arrays[index].length);
|
||||
|
||||
array.push(1);
|
||||
return Ctor ? merge({ value: array }, { value: new Ctor(buffer) }) : false;
|
||||
});
|
||||
|
||||
expect(Array.isArray(actual)).toBe(true);
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should assign `null` values', () => {
|
||||
const actual = merge({ a: 1 }, { a: null });
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(actual.a).toBe(null);
|
||||
});
|
||||
|
||||
it('should assign non array/buffer/typed-array/plain-object source values directly', () => {
|
||||
function Foo() {}
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const values = [new Foo(), new Boolean(), new Date(), Foo, new Number(), new String(), new RegExp()];
|
||||
const expected = values.map(stubTrue);
|
||||
|
||||
const actual = values.map(value => {
|
||||
const object = merge({}, { a: value, b: { c: value } });
|
||||
return object.a === value && object.b.c === value;
|
||||
});
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should clone buffer source values', () => {
|
||||
const buffer = Buffer.from([1]);
|
||||
const actual = merge({}, { value: buffer }).value;
|
||||
|
||||
expect(Buffer.isBuffer(actual)).toBe(true);
|
||||
expect(actual[0]).toBe(buffer[0]);
|
||||
expect(actual).not.toBe(buffer);
|
||||
});
|
||||
|
||||
it('should deep clone array/typed-array/plain-object source values', () => {
|
||||
const typedArray = Uint8Array ? new Uint8Array([1]) : { buffer: [1] };
|
||||
|
||||
const props = ['0', 'buffer', 'a'];
|
||||
const values: any = [[{ a: 1 }], typedArray, { a: [1] }];
|
||||
const expected = values.map(stubTrue);
|
||||
|
||||
const actual = values.map((value: any, index: any) => {
|
||||
const key = props[index];
|
||||
const object = merge({}, { value: value });
|
||||
const subValue = value[key];
|
||||
const newValue = object.value;
|
||||
const newSubValue = newValue[key];
|
||||
|
||||
return newValue !== value && newSubValue !== subValue && isEqual(newValue, value);
|
||||
});
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should not augment source objects', () => {
|
||||
var source1: any = { a: [{ a: 1 }] };
|
||||
var source2: any = { a: [{ b: 2 }] };
|
||||
var actual = merge({}, source1, source2);
|
||||
|
||||
expect(source1.a).toEqual([{ a: 1 }]);
|
||||
expect(source2.a).toEqual([{ b: 2 }]);
|
||||
expect(actual.a).toEqual([{ a: 1, b: 2 }]);
|
||||
|
||||
source1 = { a: [[1, 2, 3]] };
|
||||
source2 = { a: [[3, 4]] };
|
||||
var actual = merge({}, source1, source2);
|
||||
|
||||
expect(source1.a).toEqual([[1, 2, 3]]);
|
||||
expect(source2.a).toEqual([[3, 4]]);
|
||||
expect(actual.a).toEqual([[3, 4, 3]]);
|
||||
});
|
||||
|
||||
it('should merge plain objects onto non-plain objects', () => {
|
||||
function Foo(object: any) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
Object.assign(this, object);
|
||||
}
|
||||
|
||||
const object = { a: 1 };
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
let actual = merge(new Foo(), object);
|
||||
|
||||
expect(actual instanceof Foo);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(actual).toEqual(new Foo(object));
|
||||
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
actual = merge([new Foo()], [object]);
|
||||
expect(actual[0] instanceof Foo);
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
expect(actual).toEqual([new Foo(object)]);
|
||||
});
|
||||
|
||||
it('should not overwrite existing values with `undefined` values of object sources', () => {
|
||||
const actual = merge({ a: 1 }, { a: undefined, b: undefined });
|
||||
expect(actual).toEqual({ a: 1, b: undefined });
|
||||
});
|
||||
|
||||
it('should not overwrite existing values with `undefined` values of array sources', () => {
|
||||
let array: any = [1];
|
||||
array[2] = 3;
|
||||
|
||||
let actual = merge([4, 5, 6], array);
|
||||
const expected = [1, 5, 3];
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
|
||||
array = [1, , 3];
|
||||
array[1] = undefined;
|
||||
|
||||
actual = merge([4, 5, 6], array);
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should skip merging when `object` and `source` are the same value', () => {
|
||||
const object = {};
|
||||
let pass = true;
|
||||
|
||||
Object.defineProperty(object, 'a', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: function () {
|
||||
pass = false;
|
||||
},
|
||||
set: function () {
|
||||
pass = false;
|
||||
},
|
||||
});
|
||||
|
||||
merge(object, object);
|
||||
expect(pass);
|
||||
});
|
||||
|
||||
it('should convert values to arrays when merging arrays of `source`', () => {
|
||||
const object = { a: { 1: 'y', b: 'z', length: 2 } };
|
||||
let actual: any = merge(object, { a: ['x'] });
|
||||
|
||||
expect(actual).toEqual({ a: ['x', 'y'] });
|
||||
|
||||
actual = merge({ a: {} }, { a: [] });
|
||||
expect(actual).toEqual({ a: [] });
|
||||
});
|
||||
|
||||
it('should convert strings to arrays when merging arrays of `source`', () => {
|
||||
const object = { a: 'abcde' };
|
||||
const actual = merge(object, { a: ['x', 'y', 'z'] });
|
||||
|
||||
expect(actual).toEqual({ a: ['x', 'y', 'z'] });
|
||||
});
|
||||
});
|
309
src/compat/object/merge.ts
Normal file
309
src/compat/object/merge.ts
Normal file
@ -0,0 +1,309 @@
|
||||
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.
|
||||
*
|
||||
* 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 {S} source - The first source object whose properties will be merged into the target object.
|
||||
* @returns {O & S} The updated target object with properties from the source object(s) merged in.
|
||||
*
|
||||
* @template O - Type of the target object.
|
||||
* @template S - Type of the first source object.
|
||||
*
|
||||
* @example
|
||||
* const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
* const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: [1, 2], b: { x: 1 } };
|
||||
* const source = { a: [3], b: { y: 2 } };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [3], b: { x: 1, y: 2 } }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: null };
|
||||
* const source = { a: [1, 2, 3] };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [1, 2, 3] }
|
||||
*/
|
||||
export function merge<O, S>(object: O, source: S): O & 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.
|
||||
*
|
||||
* 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: { x: 1, y: 2 } };
|
||||
* const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: [1, 2], b: { x: 1 } };
|
||||
* const source = { a: [3], b: { y: 2 } };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [3], b: { x: 1, y: 2 } }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: null };
|
||||
* const source = { a: [1, 2, 3] };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [1, 2, 3] }
|
||||
*/
|
||||
export function merge<O, S1, S2>(object: O, source1: S1, source2: S2): 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.
|
||||
*
|
||||
* 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: { x: 1, y: 2 } };
|
||||
* const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: [1, 2], b: { x: 1 } };
|
||||
* const source = { a: [3], b: { y: 2 } };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [3], b: { x: 1, y: 2 } }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: null };
|
||||
* const source = { a: [1, 2, 3] };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [1, 2, 3] }
|
||||
*/
|
||||
export function merge<O, S1, S2, S3>(object: O, source1: S1, source2: S2, source3: S3): 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.
|
||||
*
|
||||
* 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: { x: 1, y: 2 } };
|
||||
* const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: [1, 2], b: { x: 1 } };
|
||||
* const source = { a: [3], b: { y: 2 } };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [3], b: { x: 1, y: 2 } }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: null };
|
||||
* const source = { a: [1, 2, 3] };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [1, 2, 3] }
|
||||
*/
|
||||
export function merge<O, S1, S2, S3, S4>(
|
||||
object: O,
|
||||
source1: S1,
|
||||
source2: S2,
|
||||
source3: S3,
|
||||
source4: S4
|
||||
): 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.
|
||||
*
|
||||
* 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: { x: 1, y: 2 } };
|
||||
* const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: [1, 2], b: { x: 1 } };
|
||||
* const source = { a: [3], b: { y: 2 } };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [3], b: { x: 1, y: 2 } }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: null };
|
||||
* const source = { a: [1, 2, 3] };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [1, 2, 3] }
|
||||
*/
|
||||
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;
|
||||
}
|
@ -33,7 +33,7 @@ export function isPlainObject(object?: unknown): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (object.toString() !== '[object Object]') {
|
||||
if (Object.prototype.toString.call(object) !== '[object Object]') {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const tag = object[Symbol.toStringTag];
|
||||
|
@ -8,3 +8,4 @@ export { flattenObject } from './flattenObject.ts';
|
||||
export { mapKeys } from './mapKeys.ts';
|
||||
export { mapValues } from './mapValues.ts';
|
||||
export { cloneDeep } from './cloneDeep.ts';
|
||||
export { merge } from './merge.ts';
|
||||
|
77
src/object/merge.spec.ts
Normal file
77
src/object/merge.spec.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { merge } from './merge';
|
||||
|
||||
describe('merge', () => {
|
||||
it('should merge properties from source object into target object', () => {
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: 3, c: 4 };
|
||||
const result = merge(target, source);
|
||||
|
||||
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
||||
});
|
||||
|
||||
it('should deeply merge nested objects', () => {
|
||||
const target = { a: { x: 1, y: 2 }, b: 2 };
|
||||
const source = { a: { y: 3, z: 4 }, c: 5 };
|
||||
const result = merge(target, source);
|
||||
|
||||
expect(result).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 2, c: 5 });
|
||||
|
||||
const names = {
|
||||
characters: [{ name: 'barney' }, { name: 'fred' }],
|
||||
};
|
||||
|
||||
const ages = {
|
||||
characters: [{ age: 36 }, { age: 40 }],
|
||||
};
|
||||
|
||||
const heights = {
|
||||
characters: [{ height: '5\'4"' }, { height: '5\'5"' }],
|
||||
};
|
||||
|
||||
const expected = {
|
||||
characters: [
|
||||
{ name: 'barney', age: 36, height: '5\'4"' },
|
||||
{ name: 'fred', age: 40, height: '5\'5"' },
|
||||
],
|
||||
};
|
||||
|
||||
expect(merge(merge(names, ages), heights)).toEqual(expected);
|
||||
|
||||
const target2 = { a: [1, 2], b: { x: 1 } };
|
||||
const source2 = { a: [3], b: { y: 2 } };
|
||||
expect(merge(target2, source2)).toEqual({ a: [3, 2], b: { x: 1, y: 2 } });
|
||||
});
|
||||
|
||||
it('should merge arrays deeply', () => {
|
||||
const target = { a: [1, 2] };
|
||||
const source = { a: [3, 4] };
|
||||
const result = merge(target, source);
|
||||
|
||||
expect(result).toEqual({ a: [3, 4] });
|
||||
});
|
||||
|
||||
it('should handle merging with null values', () => {
|
||||
const target = { a: null };
|
||||
const source = { a: [1, 2, 3] };
|
||||
const result = merge(target, source);
|
||||
|
||||
expect(result).toEqual({ a: [1, 2, 3] });
|
||||
});
|
||||
|
||||
it('should not overwrite existing values with undefined from source', () => {
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: undefined, c: 3 };
|
||||
const result = merge(target, source);
|
||||
|
||||
expect(result).toEqual({ a: 1, b: 2, c: 3 });
|
||||
});
|
||||
|
||||
it('should handle merging of deeply nested objects with arrays and objects', () => {
|
||||
const target = { a: { b: { c: [1] } } };
|
||||
const source = { a: { b: { c: [2], d: 3 }, e: [4] } };
|
||||
const result = merge(target, source);
|
||||
|
||||
expect(result).toEqual({ a: { b: { c: [2], d: 3 }, e: [4] } });
|
||||
});
|
||||
});
|
61
src/object/merge.ts
Normal file
61
src/object/merge.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { isObjectLike } from '../compat/predicate/isObjectLike.ts';
|
||||
|
||||
/**
|
||||
* Merges the properties of the source object into the target object.
|
||||
*
|
||||
* This function performs a deep merge, meaning nested objects and arrays are merged recursively.
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @template T - Type of the target object.
|
||||
* @template S - Type of the source object.
|
||||
*
|
||||
* @example
|
||||
* const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
* const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: [1, 2], b: { x: 1 } };
|
||||
* const source = { a: [3], b: { y: 2 } };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [3, 2], b: { x: 1, y: 2 } }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: null };
|
||||
* const source = { a: [1, 2, 3] };
|
||||
*
|
||||
* const result = merge(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [1, 2, 3] }
|
||||
*/
|
||||
export function merge<T, S>(target: T, source: S): T & S;
|
||||
export function merge(target: any, source: 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];
|
||||
|
||||
if (Array.isArray(sourceValue)) {
|
||||
target[key] = merge(targetValue ?? [], sourceValue);
|
||||
} else if (isObjectLike(targetValue) && isObjectLike(sourceValue)) {
|
||||
target[key] = merge(targetValue ?? {}, sourceValue);
|
||||
} else if (targetValue === undefined || sourceValue !== undefined) {
|
||||
target[key] = sourceValue;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
87
src/object/toMerged.spec.ts
Normal file
87
src/object/toMerged.spec.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { toMerged } from './toMerged';
|
||||
|
||||
describe('toMerged', () => {
|
||||
it('should merge properties from source object into target object', () => {
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: 3, c: 4 };
|
||||
const result = toMerged(target, source);
|
||||
|
||||
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
||||
expect(target).toEqual({ a: 1, b: 2 });
|
||||
});
|
||||
|
||||
it('should deeply merge nested objects', () => {
|
||||
const target = { a: { x: 1, y: 2 }, b: 2 };
|
||||
const source = { a: { y: 3, z: 4 }, c: 5 };
|
||||
const result = toMerged(target, source);
|
||||
|
||||
expect(result).toEqual({ a: { x: 1, y: 3, z: 4 }, b: 2, c: 5 });
|
||||
expect(target).toEqual({ a: { x: 1, y: 2 }, b: 2 });
|
||||
|
||||
const names = {
|
||||
characters: [{ name: 'barney' }, { name: 'fred' }],
|
||||
};
|
||||
|
||||
const ages = {
|
||||
characters: [{ age: 36 }, { age: 40 }],
|
||||
};
|
||||
|
||||
const heights = {
|
||||
characters: [{ height: '5\'4"' }, { height: '5\'5"' }],
|
||||
};
|
||||
|
||||
const expected = {
|
||||
characters: [
|
||||
{ name: 'barney', age: 36, height: '5\'4"' },
|
||||
{ name: 'fred', age: 40, height: '5\'5"' },
|
||||
],
|
||||
};
|
||||
|
||||
expect(toMerged(toMerged(names, ages), heights)).toEqual(expected);
|
||||
expect(names).toEqual({
|
||||
characters: [{ name: 'barney' }, { name: 'fred' }],
|
||||
});
|
||||
|
||||
const target2 = { a: [1, 2], b: { x: 1 } };
|
||||
const source2 = { a: [3], b: { y: 2 } };
|
||||
expect(toMerged(target2, source2)).toEqual({ a: [3, 2], b: { x: 1, y: 2 } });
|
||||
expect(target2).toEqual({ a: [1, 2], b: { x: 1 } });
|
||||
});
|
||||
|
||||
it('should merge arrays deeply', () => {
|
||||
const target = { a: [1, 2] };
|
||||
const source = { a: [3, 4] };
|
||||
const result = toMerged(target, source);
|
||||
|
||||
expect(result).toEqual({ a: [3, 4] });
|
||||
expect(target).toEqual({ a: [1, 2] });
|
||||
});
|
||||
|
||||
it('should handle merging with null values', () => {
|
||||
const target = { a: null };
|
||||
const source = { a: [1, 2, 3] };
|
||||
const result = toMerged(target, source);
|
||||
|
||||
expect(result).toEqual({ a: [1, 2, 3] });
|
||||
expect(target).toEqual({ a: null });
|
||||
});
|
||||
|
||||
it('should not overwrite existing values with undefined from source', () => {
|
||||
const target = { a: 1, b: 2 };
|
||||
const source = { b: undefined, c: 3 };
|
||||
const result = toMerged(target, source);
|
||||
|
||||
expect(result).toEqual({ a: 1, b: 2, c: 3 });
|
||||
expect(target).toEqual({ a: 1, b: 2 });
|
||||
});
|
||||
|
||||
it('should handle merging of deeply nested objects with arrays and objects', () => {
|
||||
const target = { a: { b: { c: [1] } } };
|
||||
const source = { a: { b: { c: [2], d: 3 }, e: [4] } };
|
||||
const result = toMerged(target, source);
|
||||
|
||||
expect(result).toEqual({ a: { b: { c: [2], d: 3 }, e: [4] } });
|
||||
expect(target).toEqual({ a: { b: { c: [1] } } });
|
||||
});
|
||||
});
|
47
src/object/toMerged.ts
Normal file
47
src/object/toMerged.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { cloneDeep } from './cloneDeep.ts';
|
||||
import { merge } from './merge.ts';
|
||||
|
||||
/**
|
||||
* Merges the properties of the source object into a deep clone of the target object.
|
||||
* Unlike `merge`, This function does not modify the original target object.
|
||||
*
|
||||
* This function performs a deep merge, meaning nested objects and arrays are merged recursively.
|
||||
*
|
||||
* - 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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @template T - Type of the target object.
|
||||
* @template S - Type of the source object.
|
||||
*
|
||||
* @example
|
||||
* const target = { a: 1, b: { x: 1, y: 2 } };
|
||||
* const source = { b: { y: 3, z: 4 }, c: 5 };
|
||||
*
|
||||
* const result = toMerged(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: [1, 2], b: { x: 1 } };
|
||||
* const source = { a: [3], b: { y: 2 } };
|
||||
*
|
||||
* const result = toMerged(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [3, 2], b: { x: 1, y: 2 } }
|
||||
*
|
||||
* @example
|
||||
* const target = { a: null };
|
||||
* const source = { a: [1, 2, 3] };
|
||||
*
|
||||
* const result = toMerged(target, source);
|
||||
* console.log(result);
|
||||
* // Output: { a: [1, 2, 3] }
|
||||
*/
|
||||
export function toMerged<T, S>(target: T, source: S): T & S;
|
||||
export function toMerged(target: any, source: any) {
|
||||
return merge(cloneDeep(target), source);
|
||||
}
|
Loading…
Reference in New Issue
Block a user