diff --git a/docs/.vitepress/items/collection.ts b/docs/.vitepress/items/collection.ts index 38e8f21..d3c90df 100644 --- a/docs/.vitepress/items/collection.ts +++ b/docs/.vitepress/items/collection.ts @@ -3,4 +3,8 @@ export const collectionItems = [ { text: 'cloneDeepWith', link: '/collection/clone-deep-with' }, { text: 'merge', link: '/collection/merge' }, { text: 'mergeWith', link: '/collection/merge-with' }, + { text: 'pick', link: '/collection/pick' }, + { text: 'pickBy', link: '/collection/pick-by' }, + { text: 'omit', link: '/collection/omit' }, + { text: 'omitBy', link: '/collection/omit-by' }, ] diff --git a/docs/collection/omit-by.md b/docs/collection/omit-by.md new file mode 100644 index 0000000..4a1cb79 --- /dev/null +++ b/docs/collection/omit-by.md @@ -0,0 +1,25 @@ +# omitBy + +Creates a new object by omitting key-value pairs based on a predicate function. + +### Usage + +```ts +import { omitBy } from 'rattail' + +omitBy({ a: 1, b: 2, c: 3 }, (value) => value > 1) +// return { a: 1 } +``` + +### Arguments + +| Arg | Type | Defaults | +| -------- | -------------------------------------- | -------- | +| `object` | `object` | | +| `fn` | `(value: any, key: string) => boolean` | | + +### Return + +| Type | +| -------- | +| `object` | diff --git a/docs/collection/omit.md b/docs/collection/omit.md new file mode 100644 index 0000000..e8c8d76 --- /dev/null +++ b/docs/collection/omit.md @@ -0,0 +1,25 @@ +# omit + +Creates a new object by omitting specified keys from an existing object. + +### Usage + +```ts +import { omit } from 'rattail' + +omit({ a: 1, b: 2, c: 3 }, ['a', 'c']) +// return { b: 2 } +``` + +### Arguments + +| Arg | Type | Defaults | +| -------- | ---------- | -------- | +| `object` | `object` | | +| `keys` | `string[]` | | + +### Return + +| Type | +| -------- | +| `object` | diff --git a/docs/collection/pick-by.md b/docs/collection/pick-by.md new file mode 100644 index 0000000..be32757 --- /dev/null +++ b/docs/collection/pick-by.md @@ -0,0 +1,25 @@ +# pickBy + +Creates a new object by picking key-value pairs based on a predicate function. + +### Usage + +```ts +import { pickBy } from 'rattail' + +pickBy({ a: 1, b: 2, c: 3 }, (value) => value > 1) +// return { b: 2, c: 3 } +``` + +### Arguments + +| Arg | Type | Defaults | +| -------- | -------------------------------------- | -------- | +| `object` | `object` | | +| `fn` | `(value: any, key: string) => boolean` | | + +### Return + +| Type | +| -------- | +| `object` | diff --git a/docs/collection/pick.md b/docs/collection/pick.md new file mode 100644 index 0000000..db9dc06 --- /dev/null +++ b/docs/collection/pick.md @@ -0,0 +1,25 @@ +# pick + +Creates a new object by selecting specified keys from an existing object. + +### Usage + +```ts +import { pick } from 'rattail' + +pick({ a: 1, b: 2, c: 3 }, ['a', 'c']) +// return { a: 1, c: 3 } +``` + +### Arguments + +| Arg | Type | Defaults | +| -------- | ---------- | -------- | +| `object` | `object` | | +| `keys` | `string[]` | | + +### Return + +| Type | +| -------- | +| `object` | diff --git a/docs/zh/collection/omit-by.md b/docs/zh/collection/omit-by.md new file mode 100644 index 0000000..1729acc --- /dev/null +++ b/docs/zh/collection/omit-by.md @@ -0,0 +1,25 @@ +# omitBy + +通过提供的谓词函数排除键值对来创建新对象。 + +### 使用 + +```ts +import { omitBy } from 'rattail' + +omitBy({ a: 1, b: 2, c: 3 }, (value) => value > 1) +// return { a: 1 } +``` + +### 参数 + +| 参数 | 类型 | 默认值 | +| -------- | -------------------------------------- | ------ | +| `object` | `object` | | +| `fn` | `(value: any, key: string) => boolean` | | + +### 返回值 + +| 类型 | +| -------- | +| `object` | diff --git a/docs/zh/collection/omit.md b/docs/zh/collection/omit.md new file mode 100644 index 0000000..26d0cac --- /dev/null +++ b/docs/zh/collection/omit.md @@ -0,0 +1,25 @@ +# omit + +通过从现有对象中排除指定键来创建新对象。 + +### 使用 + +```ts +import { omit } from 'rattail' + +omit({ a: 1, b: 2, c: 3 }, ['a', 'c']) +// return { b: 2 } +``` + +### 参数 + +| 参数 | 类型 | 默认值 | +| -------- | ---------- | ------ | +| `object` | `object` | | +| `keys` | `string[]` | | + +### 返回值 + +| 类型 | +| -------- | +| `object` | diff --git a/docs/zh/collection/pick-by.md b/docs/zh/collection/pick-by.md new file mode 100644 index 0000000..a6bd2ca --- /dev/null +++ b/docs/zh/collection/pick-by.md @@ -0,0 +1,25 @@ +# pickBy + +通过提供的谓词函数选择键值对来创建新对象。 + +### 使用 + +```ts +import { pickBy } from 'rattail' + +pickBy({ a: 1, b: 2, c: 3 }, (value) => value > 1) +// return { b: 2, c: 3 } +``` + +### 参数 + +| 参数 | 类型 | 默认值 | +| -------- | -------------------------------------- | ------ | +| `object` | `object` | | +| `fn` | `(value: any, key: string) => boolean` | | + +### 返回值 + +| 类型 | +| -------- | +| `object` | diff --git a/docs/zh/collection/pick.md b/docs/zh/collection/pick.md new file mode 100644 index 0000000..4e862ae --- /dev/null +++ b/docs/zh/collection/pick.md @@ -0,0 +1,25 @@ +# pick + +通过从现有对象中选择指定键来创建新对象。 + +### 使用 + +```ts +import { pick } from 'rattail' + +pick({ a: 1, b: 2, c: 3 }, ['a', 'c']) +// return { a: 1, c: 3 } +``` + +### 参数 + +| 参数 | 类型 | 默认值 | +| -------- | ---------- | ------ | +| `object` | `object` | | +| `keys` | `string[]` | | + +### 返回值 + +| 类型 | +| -------- | +| `object` | diff --git a/src/collection.ts b/src/collection.ts index 7073761..05493b8 100644 --- a/src/collection.ts +++ b/src/collection.ts @@ -167,3 +167,72 @@ export function cloneDeepWith(value: T, fn: (value: any) => any): T { return baseCloneDeep(value, cache) } + +export function pick(object: T, keys: K[]): Pick { + return keys.reduce( + (result, key) => { + result[key] = object[key] + return result + }, + {} as Pick, + ) +} + +export function pickBy( + collection: T, + fn: (value: T extends any[] ? T[number] : T[keyof T], key: keyof T | number) => boolean, +): Partial | T[] | undefined { + if (Array.isArray(collection)) { + return collection.reduce((result, value, index) => { + if (fn(value as T extends any[] ? T[number] : T[keyof T], index)) { + ;(result as T[]).push(value) + } + return result + }, [] as T[]) + } + + if (typeof collection === 'object' && collection !== null) { + const allKeys = [...Object.keys(collection), ...Object.getOwnPropertySymbols(collection)] as (keyof T)[] + return allKeys.reduce((result, key) => { + const value = collection[key as keyof T] + if (fn(value as T extends any[] ? T[number] : T[keyof T], key)) { + ;(result as Partial)[key] = value + } + return result + }, {} as Partial) + } +} + +export function omit(object: T, keys: K[]): Omit { + const keysToOmit = new Set(keys) + const result = {} as Omit + const ownKeys = [...Object.keys(object), ...Object.getOwnPropertySymbols(object)] as (keyof T)[] + + ownKeys.forEach((key) => { + if (!keysToOmit.has(key as K)) { + ;(result as T)[key] = object[key] + } + }) + + return result +} + +export function omitBy( + collection: T, + fn: (value: T extends any[] ? T[number] : T[keyof T], key: number | keyof T) => boolean, +): Partial | T[] | undefined { + if (Array.isArray(collection)) { + return collection.filter((value, index) => !fn(value as T extends any[] ? T[number] : T[keyof T], index)) + } + + if (typeof collection === 'object' && collection !== null) { + const allKeys = [...Object.keys(collection), ...Object.getOwnPropertySymbols(collection)] as (keyof T)[] + return allKeys.reduce((result, key) => { + const value = collection[key] + if (!fn(value as T extends any[] ? T[number] : T[keyof T], key)) { + ;(result as Partial)[key] = value + } + return result + }, {} as Partial) + } +} diff --git a/tests/collection.spec.ts b/tests/collection.spec.ts index 0f5c15e..5698003 100644 --- a/tests/collection.spec.ts +++ b/tests/collection.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest' -import { merge, mergeWith, cloneDeep, cloneDeepWith, isNumber, hasOwn } from '../src' +import { merge, mergeWith, cloneDeep, cloneDeepWith, isNumber, hasOwn, pick, omit, pickBy, omitBy } from '../src' it('should merge two objects', () => { const result = merge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 }) @@ -344,3 +344,114 @@ describe('cloneDeepWith', () => { expect(result).toEqual({ a: 2, b: 3 }) }) }) + +describe('pick', () => { + it('should pick specified string keys from an object', () => { + const obj = { a: 1, b: 2, c: 3 } + const result = pick(obj, ['a', 'c']) + expect(result).toEqual({ a: 1, c: 3 }) + }) + + it('should pick specified symbol keys from an object', () => { + const symA = Symbol('a') + const symB = Symbol('b') + const obj = { + [symA]: 'valueA', + [symB]: 'valueB', + c: 3, + } + + const result = pick(obj, [symA]) + expect(result).toEqual({ + [symA]: 'valueA', + }) + }) + + it('should pick elements by index from an array', () => { + const arr = [10, 20, 30, 40, 50] + const result = pick(arr, [0, 2, 4]) + expect(result).toEqual([10, 30, 50]) + }) +}) + +describe('omit', () => { + it('should omit specified string keys from an object', () => { + const obj = { a: 1, b: 2, c: 3 } + const result = omit(obj, ['b']) + expect(result).toEqual({ a: 1, c: 3 }) + }) + + it('should omit specified symbol keys from an object', () => { + const symA = Symbol('a') + const symB = Symbol('b') + const obj = { + [symA]: 'valueA', + [symB]: 'valueB', + c: 3, + } + + const result = omit(obj, [symA]) + expect(result).toEqual({ + [symB]: 'valueB', + c: 3, + }) + }) + + it('should omit elements by index from an array', () => { + const arr = [10, 20, 30, 40, 50] + const result = omit(arr, [1, 3]) + expect(result).toEqual([10, 30, 50]) + }) +}) + +describe('pickBy', () => { + it('should pick properties from an object based on the predicate for string keys', () => { + const obj = { a: 1, b: 2, c: 3 } + const result = pickBy(obj, (value) => value % 2 === 1) + expect(result).toEqual({ a: 1, c: 3 }) + }) + + it('should pick properties from an object based on the predicate for symbol keys', () => { + const symA = Symbol('a') + const symB = Symbol('b') + const obj = { + [symA]: 1, + [symB]: 2, + c: 3, + } + const result = pickBy(obj, (value) => value === 2) + expect(result).toEqual({ [symB]: 2 }) + }) + + it('should pick elements from an array based on the predicate', () => { + const arr: number[] = [10, 15, 20, 25, 30] // Explicitly specify the array as number[] + const result = pickBy(arr, (value) => value > 15) + expect(result).toEqual([20, 25, 30]) + }) +}) + +describe('omitBy', () => { + it('should omit properties from an object based on the predicate for string keys', () => { + const obj = { a: 1, b: 2, c: 3 } + const result = omitBy(obj, (value) => value % 2 === 1) + expect(result).toEqual({ b: 2 }) + }) + + it('should omit properties from an object based on the predicate for symbol keys', () => { + const symA = Symbol('a') + const symB = Symbol('b') + const obj = { + [symA]: 1, + [symB]: 2, + c: 3, + } + const result = omitBy(obj, (value) => value === 2) + expect(result).toEqual({ [symA]: 1, c: 3 }) + }) + + it('should omit elements from an array based on the predicate', () => { + const arr = [10, 15, 20, 25, 30] + const result = omitBy(arr, (value) => value < 20) + expect(result).toEqual([20, 25, 30]) + }) +})