fix(flatten/flattenDeep/flattenDepth/slice/zipObjectDeep): fix lodash compatibility (#716)

This commit is contained in:
D-Sketon 2024-10-16 09:04:17 +08:00 committed by GitHub
parent 904ca1a2b4
commit 61f5bfd7bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 170 additions and 40 deletions

View File

@ -11,12 +11,22 @@
## インターフェース ## インターフェース
```typescript ```typescript
function flattenDepth<T, D extends number = 1>(value: T[] | object, depth: D): Array<FlatArray<T[], D>> | []; function flattenDepth<T, D extends number = 1>(value: T[], depth: D): Array<FlatArray<T[], D>> | [];
``` ```
### パラメータ ### パラメータ
- `value` (`T[] | object`): フラット化する値。 - `value` (`T[]`): フラット化する値。
::: info `value``ArrayLike<T>`、`null`、または `undefined` になります。
lodash との完全な互換性を確保するため、`flattenDepth` 関数は `value` を次のように処理します。
- `value``ArrayLike<T>` の場合、`Array.from(...)` を使用して配列に変換されます。
- `value``null` または `undefined` の場合、空の配列として扱われます。
:::
- `depth` (`D`): ネストされた配列構造をどの深さまでフラット化するかを指定する深さレベル。デフォルトは1です。 - `depth` (`D`): ネストされた配列構造をどの深さまでフラット化するかを指定する深さレベル。デフォルトは1です。
### 戻り値 ### 戻り値

View File

@ -19,6 +19,16 @@ function slice<T>(array: T[], start?: number, end?: number): T[];
### パラメータ ### パラメータ
- `array` (`T[]`): 部分配列を作成する配列。 - `array` (`T[]`): 部分配列を作成する配列。
::: info `array``ArrayLike<T>`、`null`、または `undefined` になります。
lodash との完全な互換性を確保するため、`slice` 関数は `array` を次のように処理します。
- `array``ArrayLike<T>` の場合、`Array.from(...)` を使用して配列に変換されます。
- `array``null` または `undefined` の場合、空の配列として扱われます。
:::
- `start` (`number`): 開始位置。デフォルトは `0` です。 - `start` (`number`): 開始位置。デフォルトは `0` です。
- `end` (`number`): 終了位置。デフォルトは `array.length` です。 - `end` (`number`): 終了位置。デフォルトは `array.length` です。

View File

@ -13,13 +13,13 @@
## インターフェース ## インターフェース
```typescript ```typescript
function zipObjectDeep<P extends string | number | symbol, V>(keys: P[], values: V[]): { [K in P]: V }; function zipObjectDeep<P extends PropertyKey, V>(keys: ArrayLike<P | P[]>, values: ArrayLike<V>): { [K in P]: V };
``` ```
### パラメータ ### パラメータ
- `keys` (`P[]`): プロパティパスを含む配列。 - `keys` (`ArrayLike<P | P[]>`): プロパティパスを含む配列。
- `values` (`V[]`): 対応する値を含む配列。 - `values` (`ArrayLike<V>`): 対応する値を含む配列。
### 戻り値 ### 戻り値

View File

@ -11,12 +11,22 @@
## 인터페이스 ## 인터페이스
```typescript ```typescript
function flattenDepth<T, D extends number = 1>(value: T[] | object, depth: D): Array<FlatArray<T[], D>> | []; function flattenDepth<T, D extends number = 1>(value: T[], depth: D): Array<FlatArray<T[], D>> | [];
``` ```
### 파라미터 ### 파라미터
- `value` (`T[] | object`): 평평하게 할 값이에요. - `value` (`T[]`): 평평하게 할 값이에요.
::: info `value``ArrayLike<T>`, `null`, 또는 `undefined`가 될 수 있어요.
lodash와 완전한 호환성을 보장하기 위해, `flattenDepth` 함수는 `value`를 다음과 같이 처리해요.
- `value``ArrayLike<T>`인 경우, `Array.from(...)`을 사용하여 배열로 변환돼요.
- `value``null` 또는 `undefined`인 경우, 빈 배열로 처리돼요.
:::
- `depth` (`D`): 중첩 배열 구조가 얼마나 깊게 평평해져야 하는지 지정하는 깊이 수준이에요. 기본값은 1이에요. - `depth` (`D`): 중첩 배열 구조가 얼마나 깊게 평평해져야 하는지 지정하는 깊이 수준이에요. 기본값은 1이에요.
### 반환 값 ### 반환 값

View File

@ -19,6 +19,16 @@ function slice<T>(array: T[], start?: number, end?: number): T[];
### 파라미터 ### 파라미터
- `array` (`T[]`): 부분 배열을 만들 배열. - `array` (`T[]`): 부분 배열을 만들 배열.
::: info `array``ArrayLike<T>`, `null`, 또는 `undefined`가 될 수 있어요.
lodash와 완전한 호환성을 보장하기 위해, `slice` 함수는 `array`를 다음과 같이 처리해요.
- `array``ArrayLike<T>`인 경우, `Array.from(...)`을 사용하여 배열로 변환돼요.
- `array``null` 또는 `undefined`인 경우, 빈 배열로 처리돼요.
:::
- `start` (`number`): 시작 위치. 기본값은 `0`이에요. - `start` (`number`): 시작 위치. 기본값은 `0`이에요.
- `end` (`number`): 끝 위치. 기본값은 `array.length`예요. - `end` (`number`): 끝 위치. 기본값은 `array.length`예요.

View File

@ -13,13 +13,13 @@
## 인터페이스 ## 인터페이스
```typescript ```typescript
function zipObjectDeep<P extends string | number | symbol, V>(keys: P[], values: V[]): { [K in P]: V }; function zipObjectDeep<P extends PropertyKey, V>(keys: ArrayLike<P | P[]>, values: ArrayLike<V>): { [K in P]: V };
``` ```
### 파라미터 ### 파라미터
- `keys` (`P[]`): 프로퍼티 경로가 포함된 배열. - `keys` (`ArrayLike<P | P[]>`): 프로퍼티 경로가 포함된 배열.
- `values` (`V[]`): 값에 대응되는 값이 포함된 배열. - `values` (`ArrayLike<V>`): 값에 대응되는 값이 포함된 배열.
### 반환 값 ### 반환 값

View File

@ -11,12 +11,22 @@ Flattens an array up to the specified depth.
## Signature ## Signature
```typescript ```typescript
function flattenDepth<T, D extends number = 1>(value: T[] | object, depth: D): Array<FlatArray<T[], D>> | []; function flattenDepth<T, D extends number = 1>(value: T[], depth: D): Array<FlatArray<T[], D>> | [];
``` ```
### Parameters ### Parameters
- `value` (`T[] | object`): The value to flatten. - `value` (`T[]`): The value to flatten.
::: info `value` can be `ArrayLike<T>`, `null`, or `undefined`
To ensure full compatibility with lodash, the `flattenDepth` function handles `value` in this way:
- If `value` is an `ArrayLike<T>`, it gets converted into an array using `Array.from(...)`.
- If `value` is `null` or `undefined`, it will be treated as an empty array.
:::
- `depth` (`D`): The depth level specifying how deep a nested array structure should be flattened. Defaults to 1. - `depth` (`D`): The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
### Returns ### Returns

View File

@ -19,6 +19,16 @@ function slice<T>(array: T[], start?: number, end?: number): T[];
### Parameters ### Parameters
- `array` (`T[]`): The array to slice. - `array` (`T[]`): The array to slice.
::: info `array` can be `ArrayLike<T>`, `null`, or `undefined`
To ensure full compatibility with lodash, the `slice` function handles `array` in this way:
- If `array` is an `ArrayLike<T>`, it gets converted into an array using `Array.from(...)`.
- If `array` is `null` or `undefined`, it will be treated as an empty array.
:::
- `start` (`number`): The start position. Defaults to `0`. - `start` (`number`): The start position. Defaults to `0`.
- `end` (`number`): The end position. Defaults to `array.length`. - `end` (`number`): The end position. Defaults to `array.length`.

View File

@ -15,13 +15,13 @@ Paths can be dot-separated strings or arrays of property names. If the `keys` ar
## Signature ## Signature
```typescript ```typescript
function zipObjectDeep<P extends string | number | symbol, V>(keys: P[], values: V[]): { [K in P]: V }; function zipObjectDeep<P extends PropertyKey, V>(keys: ArrayLike<P | P[]>, values: ArrayLike<V>): { [K in P]: V };
``` ```
### Parameters ### Parameters
- `keys` (`P[]`): An array of property names. - `keys` (`ArrayLike<P | P[]>`): An array of property names.
- `values` (`V[]`): An array of values corresponding to the property names. - `values` (`ArrayLike<V>`): An array of values corresponding to the property names.
### Returns ### Returns

View File

@ -11,12 +11,22 @@
## 签名 ## 签名
```typescript ```typescript
function flattenDepth<T, D extends number = 1>(value: T[] | object, depth: D): Array<FlatArray<T[], D>> | []; function flattenDepth<T, D extends number = 1>(value: T[], depth: D): Array<FlatArray<T[], D>> | [];
``` ```
### 参数 ### 参数
- `value` (`T[] | object`): 要展平的值。 - `value` (`T[]`): 要展平的值。
::: info `value` 可以是 `ArrayLike<T>`、`null` 或 `undefined`
为了确保与 lodash 的完全兼容性,`flattenDepth` 函数以以下方式处理 `value`
- 如果 `value``ArrayLike<T>`,则会使用 `Array.from(...)` 将其转换为数组。
- 如果 `value``null``undefined`,则会将其视为一个空数组。
:::
- `depth` (`D`): 指定嵌套数组结构展平深度的级别。默认值为1。 - `depth` (`D`): 指定嵌套数组结构展平深度的级别。默认值为1。
### 返回值 ### 返回值

View File

@ -19,6 +19,16 @@ function slice<T>(array: T[], start?: number, end?: number): T[];
### 参数 ### 参数
- `array` (`T[]`): 用于创建部分数组的数组。 - `array` (`T[]`): 用于创建部分数组的数组。
::: info `array` 可以是 `ArrayLike<T>`、`null` 或 `undefined`
为了确保与 lodash 的完全兼容性,`slice` 函数以以下方式处理 `array`
- 如果 `array``ArrayLike<T>`,则会使用 `Array.from(...)` 将其转换为数组。
- 如果 `array``null``undefined`,则会将其视为一个空数组。
:::
- `start` (`number`): 开始位置。默认值为 `0` - `start` (`number`): 开始位置。默认值为 `0`
- `end` (`number`): 结束位置。默认值为 `array.length` - `end` (`number`): 结束位置。默认值为 `array.length`

View File

@ -14,13 +14,13 @@
## 签名 ## 签名
```typescript ```typescript
function zipObjectDeep<P extends string | number | symbol, V>(keys: P[], values: V[]): { [K in P]: V }; function zipObjectDeep<P extends PropertyKey, V>(keys: ArrayLike<P | P[]>, values: ArrayLike<V>): { [K in P]: V };
``` ```
### 参数 ### 参数
- `keys` (`P[]`): 属性名称的数组。 - `keys` (`ArrayLike<P | P[]>`): 属性名称的数组。
- `values` (`V[]`): 与属性名称对应的值的数组。 - `values` (`ArrayLike<V>`): 与属性名称对应的值的数组。
### 返回值 ### 返回值

View File

@ -47,8 +47,14 @@ describe('flatten', () => {
it('should return an empty array for non array-like objects', () => { it('should return an empty array for non array-like objects', () => {
const nonArray = { 0: 'a' }; const nonArray = { 0: 'a' };
const expected: [] = []; const expected: [] = [];
const actual = flatten(nonArray); const actual = flatten(nonArray as any);
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
it('should support array-like', () => {
expect(flatten({ 0: [1, 2, 3], length: 1 })).toEqual([1, 2, 3]);
expect(flatten('123')).toEqual(['1', '2', '3']);
expect(flatten(args)).toEqual([1, 2, 3]);
});
}); });

View File

@ -1,9 +1,11 @@
import { isArrayLike } from '../predicate/isArrayLike.ts';
/** /**
* Flattens an array up to the specified depth. * Flattens an array up to the specified depth.
* *
* @template T - The type of elements within the array. * @template T - The type of elements within the array.
* @template D - The depth to which the array should be flattened. * @template D - The depth to which the array should be flattened.
* @param {T[] | object} value - The object to flatten. * @param {ArrayLike<T> | null | undefined} value - The object to flatten.
* @param {D} depth - The depth level specifying how deep a nested array structure should be flattened. Defaults to 1. * @param {D} depth - The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
* @returns {Array<FlatArray<T[], D>> | []} A new array that has been flattened. * @returns {Array<FlatArray<T[], D>> | []} A new array that has been flattened.
* *
@ -15,13 +17,13 @@
* // Returns: [1, 2, 3, 4, 5, 6] * // Returns: [1, 2, 3, 4, 5, 6]
*/ */
export function flatten<T, D extends number = 1>( export function flatten<T, D extends number = 1>(
value: readonly T[] | object, value: ArrayLike<T> | null | undefined,
depth = 1 as D depth = 1 as D
): Array<FlatArray<T[], D>> | [] { ): Array<FlatArray<T[], D>> | [] {
const result: Array<FlatArray<T[], D>> = []; const result: Array<FlatArray<T[], D>> = [];
const flooredDepth = Math.floor(depth); const flooredDepth = Math.floor(depth);
if (!Array.isArray(value)) { if (!isArrayLike(value)) {
return result; return result;
} }
@ -45,7 +47,7 @@ export function flatten<T, D extends number = 1>(
} }
}; };
recursive(value, 0); recursive(Array.from(value), 0);
return result; return result;
} }

View File

@ -47,8 +47,14 @@ describe('flattenDeep', () => {
it('should return an empty array for non array-like objects', () => { it('should return an empty array for non array-like objects', () => {
const nonArray = { 0: 'a' }; const nonArray = { 0: 'a' };
const expected: [] = []; const expected: [] = [];
const actual = flattenDeep(nonArray); const actual = flattenDeep(nonArray as any);
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
it('should support array-like', () => {
expect(flattenDeep({ 0: [1, 2, [3]], length: 1 })).toEqual([1, 2, 3]);
expect(flattenDeep('123')).toEqual(['1', '2', '3']);
expect(flattenDeep(args)).toEqual([1, 2, 3]);
});
}); });

View File

@ -16,13 +16,13 @@ type ExtractNestedArrayType<T> = T extends ReadonlyArray<infer U> ? ExtractNeste
* Flattens all depths of a nested array. * Flattens all depths of a nested array.
* *
* @template T - The type of elements within the array. * @template T - The type of elements within the array.
* @param {T[] | object} value - The value to flatten. * @param {ArrayLike<T>} value - The value to flatten.
* @returns {Array<ExtractNestedArrayType<T>> | []} A new array that has been flattened. * @returns {Array<ExtractNestedArrayType<T>> | []} A new array that has been flattened.
* *
* @example * @example
* const value = flattenDeep([1, [2, [3]], [4, [5, 6]]]); * const value = flattenDeep([1, [2, [3]], [4, [5, 6]]]);
* // Returns: [1, 2, 3, 4, 5, 6] * // Returns: [1, 2, 3, 4, 5, 6]
*/ */
export function flattenDeep<T>(value: readonly T[] | object): Array<ExtractNestedArrayType<T>> | [] { export function flattenDeep<T>(value: ArrayLike<T> | null | undefined): Array<ExtractNestedArrayType<T>> | [] {
return flatten(value, Infinity) as Array<ExtractNestedArrayType<T>>; return flatten(value, Infinity) as Array<ExtractNestedArrayType<T>>;
} }

View File

@ -47,7 +47,7 @@ describe('flattenDepth', () => {
it('should return an empty array for non array-like objects', () => { it('should return an empty array for non array-like objects', () => {
const nonArray = { 0: 'a' }; const nonArray = { 0: 'a' };
const expected: [] = []; const expected: [] = [];
const actual = flattenDepth(nonArray, 2); const actual = flattenDepth(nonArray as any, 2);
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
@ -68,4 +68,10 @@ describe('flattenDepth', () => {
const array = [1, [2, [3, [4]], 5]]; const array = [1, [2, [3, [4]], 5]];
expect(flattenDepth(array, 2.2)).toEqual([1, 2, 3, [4], 5]); expect(flattenDepth(array, 2.2)).toEqual([1, 2, 3, [4], 5]);
}); });
it('should support array-like', () => {
expect(flattenDepth({ 0: [1, 2, 3], length: 1 })).toEqual([1, 2, 3]);
expect(flattenDepth('123')).toEqual(['1', '2', '3']);
expect(flattenDepth(args)).toEqual([1, 2, 3]);
});
}); });

View File

@ -5,7 +5,7 @@ import { flatten } from './flatten.ts';
* *
* @template T - The type of elements within the array. * @template T - The type of elements within the array.
* @template D - The depth to which the array should be flattened. * @template D - The depth to which the array should be flattened.
* @param {T[] | object} value - The value to flatten. * @param {ArrayLike<T> | null | undefined} value - The value to flatten.
* @param {D} depth - The depth level specifying how deep a nested array structure should be flattened. Defaults to 1. * @param {D} depth - The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
* @returns {Array<FlatArray<T[], D>> | []} A new array that has been flattened. * @returns {Array<FlatArray<T[], D>> | []} A new array that has been flattened.
* *
@ -17,7 +17,7 @@ import { flatten } from './flatten.ts';
* // Returns: [1, 2, 3, 4, 5, 6] * // Returns: [1, 2, 3, 4, 5, 6]
*/ */
export function flattenDepth<T, D extends number = 1>( export function flattenDepth<T, D extends number = 1>(
value: readonly T[] | object, value: ArrayLike<T> | null | undefined,
depth = 1 as D depth = 1 as D
): Array<FlatArray<T[], D>> | [] { ): Array<FlatArray<T[], D>> | [] {
return flatten(value, depth); return flatten(value, depth);

View File

@ -1,5 +1,6 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { slice } from './slice'; import { slice } from './slice';
import { args } from '../_internal/args';
import { falsey } from '../_internal/falsey'; import { falsey } from '../_internal/falsey';
describe('slice', () => { describe('slice', () => {
@ -101,4 +102,10 @@ describe('slice', () => {
expect(slice(null as any)).toEqual([]); expect(slice(null as any)).toEqual([]);
expect(slice(undefined as any)).toEqual([]); expect(slice(undefined as any)).toEqual([]);
}); });
it('should support array-like', () => {
expect(slice({ 0: 1, 1: 2, 2: 3, length: 3 })).toEqual([1, 2, 3]);
expect(slice('123')).toEqual(['1', '2', '3']);
expect(slice(args)).toEqual([1, 2, 3]);
});
}); });

View File

@ -1,4 +1,5 @@
import { isIterateeCall } from '../_internal/isIterateeCall.ts'; import { isIterateeCall } from '../_internal/isIterateeCall.ts';
import { isArrayLike } from '../predicate/isArrayLike.ts';
import { toInteger } from '../util/toInteger.ts'; import { toInteger } from '../util/toInteger.ts';
/** /**
@ -7,7 +8,7 @@ import { toInteger } from '../util/toInteger.ts';
* It does not return a dense array for sparse arrays unlike the native `Array.prototype.slice`. * It does not return a dense array for sparse arrays unlike the native `Array.prototype.slice`.
* *
* @template T - The type of the array elements. * @template T - The type of the array elements.
* @param {T[]} array - The array to slice. * @param {ArrayLike<T> | null | undefined} array - The array to slice.
* @param {number} [start=0] - The start position. * @param {number} [start=0] - The start position.
* @param {number} [end=array.length] - The end position. * @param {number} [end=array.length] - The end position.
* @returns {T[]} - Returns the slice of `array`. * @returns {T[]} - Returns the slice of `array`.
@ -16,8 +17,8 @@ import { toInteger } from '../util/toInteger.ts';
* slice([1, 2, 3], 1, 2); // => [2] * slice([1, 2, 3], 1, 2); // => [2]
* slice(new Array(3)); // => [undefined, undefined, undefined] * slice(new Array(3)); // => [undefined, undefined, undefined]
*/ */
export function slice<T>(array: readonly T[], start?: number, end?: number): T[] { export function slice<T>(array: ArrayLike<T> | null | undefined, start?: number, end?: number): T[] {
if (array == null) { if (!isArrayLike(array)) {
return []; return [];
} }

View File

@ -37,4 +37,19 @@ describe('zipObject', () => {
it('should support deep paths', () => { it('should support deep paths', () => {
expect(zipObjectDeep(['a.b.c'], [1])).toEqual({ a: { b: { c: 1 } } }); expect(zipObjectDeep(['a.b.c'], [1])).toEqual({ a: { b: { c: 1 } } });
}); });
it('should return an empty object when given null or undefined `keys`', () => {
expect(zipObjectDeep(null as any, [1])).toEqual({});
expect(zipObjectDeep(undefined as any, [1])).toEqual({});
});
it('should support array-like keys', () => {
expect(zipObjectDeep({ 0: ['a'], length: 1 }, [1])).toEqual({ a: 1 });
expect(zipObjectDeep('12', [1, 2])).toEqual({ 1: 1, 2: 2 });
});
it('should support array-like values', () => {
expect(zipObjectDeep(['a'], { 0: 1, length: 1 })).toEqual({ a: 1 });
expect(zipObjectDeep(['a', 'b'], '12')).toEqual({ a: '1', b: '2' });
});
}); });

View File

@ -1,5 +1,6 @@
import { zip } from '../../array/zip.ts'; import { zip } from '../../array/zip.ts';
import { set } from '../object/set.ts'; import { set } from '../object/set.ts';
import { isArrayLike } from '../predicate/isArrayLike.ts';
/** /**
* Creates a deeply nested object given arrays of paths and values. * Creates a deeply nested object given arrays of paths and values.
@ -12,9 +13,9 @@ import { set } from '../object/set.ts';
* *
* @template P - The type of property paths. * @template P - The type of property paths.
* @template V - The type of values corresponding to the property paths. * @template V - The type of values corresponding to the property paths.
* @param {P[] | P[][]} keys - An array of property paths, each path can be a dot-separated string or an array of property names. * @param {ArrayLike<P | P[]>} keys - An array of property paths, each path can be a dot-separated string or an array of property names.
* @param {V[]} values - An array of values corresponding to the property paths. * @param {ArrayLike<V>} values - An array of values corresponding to the property paths.
* @returns {object} A new object composed of the given property paths and values. * @returns {{ [K in P]: V }} A new object composed of the given property paths and values.
* *
* @example * @example
* const paths = ['a.b.c', 'd.e.f']; * const paths = ['a.b.c', 'd.e.f'];
@ -35,11 +36,17 @@ import { set } from '../object/set.ts';
* // result will be { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } } * // result will be { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
*/ */
export function zipObjectDeep<P extends PropertyKey, V>( export function zipObjectDeep<P extends PropertyKey, V>(
keys: readonly P[] | readonly P[][], keys: ArrayLike<P | P[]>,
values: readonly V[] values: ArrayLike<V>
): { [K in P]: V } { ): { [K in P]: V } {
const result = {} as { [K in P]: V }; const result = {} as { [K in P]: V };
const zipped = zip<P | P[], V>(keys, values); if (!isArrayLike(keys)) {
return result;
}
if (!isArrayLike(values)) {
values = [];
}
const zipped = zip<P | P[], V>(Array.from(keys), Array.from(values));
for (let i = 0; i < zipped.length; i++) { for (let i = 0; i < zipped.length; i++) {
const [key, value] = zipped[i]; const [key, value] = zipped[i];