From eda4fc3d30162ee3ff11d2eca205d87bd7a45759 Mon Sep 17 00:00:00 2001 From: hyunwoo Date: Fri, 15 Nov 2024 07:39:29 +0900 Subject: [PATCH 1/2] feat(findKey): add findKey to compat layer --- benchmarks/performance/findKey.bench.ts | 9 ++ src/compat/index.ts | 1 + src/compat/object/findKey.spec.ts | 61 +++++++++++ src/compat/object/findKey.ts | 132 ++++++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 src/compat/object/findKey.spec.ts create mode 100644 src/compat/object/findKey.ts diff --git a/benchmarks/performance/findKey.bench.ts b/benchmarks/performance/findKey.bench.ts index 22ed0ef22..4b33118be 100644 --- a/benchmarks/performance/findKey.bench.ts +++ b/benchmarks/performance/findKey.bench.ts @@ -1,5 +1,6 @@ import { bench, describe } from 'vitest'; import { findKey as findKeyToolkit } from 'es-toolkit'; +import { findKey as findKeyCompatToolkit } from 'es-toolkit/compat'; import { findKey as findKeyLodash } from 'lodash'; describe('findKey', () => { @@ -16,6 +17,10 @@ describe('findKey', () => { bench('lodash/findKey', () => { findKeyLodash(users, o => o.age < 40); }); + + bench('es-toolkit/compat/findKey', () => { + findKeyCompatToolkit(users, o => o.age < 40); + }); }); describe('findKey/largeObject', () => { @@ -31,4 +36,8 @@ describe('findKey/largeObject', () => { bench('lodash/findKey', () => { findKeyLodash(largeUsers, o => o.age === 7000); }); + + bench('es-toolkit/compat/findKey', () => { + findKeyCompatToolkit(largeUsers, o => o.age === 7000); + }); }); diff --git a/src/compat/index.ts b/src/compat/index.ts index 15be574b7..1f7bd0d7d 100644 --- a/src/compat/index.ts +++ b/src/compat/index.ts @@ -115,6 +115,7 @@ export { property } from './object/property.ts'; export { set } from './object/set.ts'; export { toDefaulted } from './object/toDefaulted.ts'; export { unset } from './object/unset.ts'; +export { findKey } from './object/findKey.ts' export { conforms } from './predicate/conforms.ts'; export { conformsTo } from './predicate/conformsTo.ts'; diff --git a/src/compat/object/findKey.spec.ts b/src/compat/object/findKey.spec.ts new file mode 100644 index 000000000..624c38f69 --- /dev/null +++ b/src/compat/object/findKey.spec.ts @@ -0,0 +1,61 @@ +import { describe, expect, it } from 'vitest'; +import { findKey } from './findKey'; + +/** + * @see https://lodash.com/docs/4.17.15#findKey + */ +describe('findKey', () => { + const users = { + barney: { age: 36, active: true }, + fred: { age: 40, active: false }, + pebbles: { age: 1, active: true }, + }; + + it('should find key with a function predicate', function () { + const actual = findKey(users, function (o) { + return o.age < 40; + }); + expect(actual).toBe('barney'); + }); + + it('should work with `_.matches` shorthands', function () { + const actual = findKey(users, { age: 1, active: true }); + expect(actual).toBe('pebbles'); + }); + + it('should work with `_.matchesProperty` shorthands', function () { + const actual = findKey(users, ['active', false]); + expect(actual).toBe('fred'); + }); + + it('should work with `_.property` shorthands', function () { + const actual = findKey(users, 'active'); + expect(actual).toBe('barney'); + }); + + it('should return undefined for an empty object', function () { + // @ts-expect-error - invalid argument + const actual = findKey({}, { age: 36 }); + expect(actual).toBeUndefined(); + }); + + it('should return undefined for null input', function () { + const actual = findKey(null, { age: 36 }); + expect(actual).toBeUndefined(); + }); + + it('should return undefined for undefined input', function () { + const actual = findKey(undefined, { age: 36 }); + expect(actual).toBeUndefined(); + }); + + it('should return undefined if no matching key is found', function () { + const actual = findKey(users, { age: 100 }); + expect(actual).toBeUndefined(); + }); + + it('should handle partial matches with `Partial`', function () { + const actual = findKey(users, { active: true }); + expect(actual).toBe('barney'); + }); +}); diff --git a/src/compat/object/findKey.ts b/src/compat/object/findKey.ts new file mode 100644 index 000000000..6db774e1f --- /dev/null +++ b/src/compat/object/findKey.ts @@ -0,0 +1,132 @@ +import { property } from './property'; +import { findKey as findKeyToolkit } from '../../object'; +import { isObject } from '../predicate/isObject'; +import { matches } from '../predicate/matches'; +import { matchesProperty } from '../predicate/matchesProperty'; + +/** + * Finds the key of the first element predicate returns truthy for. + * + * @template T - The type of the object. + * @param {T | null | undefined} obj - The object to inspect. + * @param {(value: T[keyof T], key: keyof T, obj: T) => boolean} conditionToFind - The function invoked per iteration. + * @returns {keyof T | undefined} Returns the key of the matched element, else `undefined`. + * + * @example + * const users = { 'barney': { 'age': 36 }, 'fred': { 'age': 40 } }; + * const result = findKey(users, o => o.age < 40); + * // => 'barney' + */ +export function findKey>( + obj: T | null | undefined, + conditionToFind: (value: T[keyof T], key: keyof T, obj: T) => boolean +): keyof T | undefined; + +/** + * Finds the key of the first element that matches the given object. + * + * @template T - The type of the object. + * @param {T | null | undefined} obj - The object to inspect. + * @param {Partail} objectToFind - The object to match. + * @returns {keyof T | undefined} Returns the key of the matched element, else `undefined`. + * + * @example + * const users = { 'barney': { 'age': 36 }, 'fred': { 'age': 40 } }; + * const result = findKey(users, { 'age': 36 }); + * // => 'barney' + */ +export function findKey>( + obj: T | null | undefined, + objectToFind: Partial +): keyof T | undefined; + +/** + * Finds the key of the first element that matches the given property and value. + * + * @template T - The type of the object. + * @param {T | null | undefined} obj - The object to inspect. + * @param {[keyof T[keyof T], any]} propertyToFind - The property and value to match. + * @returns {keyof T | undefined} Returns the key of the matched element, else `undefined`. + * + * @example + * const users = { 'barney': { 'age': 36 }, 'fred': { 'age': 40 } }; + * const result = findKey(users, ['age', 36]); + * // => 'barney' + */ +export function findKey>( + obj: T | null | undefined, + propertyToFind: [keyof T[keyof T], any] +): keyof T | undefined; + +/** + * Finds the key of the first element that has a truthy value for the given property. + * + * @template T - The type of the object. + * @param {T | null | undefined} obj - The object to inspect. + * @param {keyof T[keyof T]} propertyToFind - The property to check. + * @returns {keyof T | undefined} Returns the key of the matched element, else `undefined`. + * + * @example + * const users = { 'barney': { 'active': true }, 'fred': { 'active': false } }; + * const result = findKey(users, 'active'); + * // => 'barney' + */ +export function findKey>( + obj: T | null | undefined, + propertyToFind: keyof T[keyof T] +): keyof T | undefined; + +/** + * Finds the key of the first element that matches the given predicate. + * + * This function determines the type of the predicate and delegates the search + * to the appropriate helper function. It supports predicates as functions, objects, + * arrays, or strings. + * + * @template T - The type of the object. + * @param {T | null | undefined} obj - The object to inspect. + * @param {(value: T[keyof T], key: keyof T, obj: T) => boolean | Partial | [keyof T[keyof T], any] | keyof T[keyof T]} predicate - The predicate to match. + * @returns {keyof T | undefined} Returns the key of the matched element, else `undefined`. + */ +export function findKey>( + obj: T | null | undefined, + predicate: + | ((value: T[keyof T], key: keyof T, obj: T) => boolean) + | Partial + | [keyof T[keyof T], any] + | keyof T[keyof T] +): keyof T | undefined { + if (!isObject(obj)) { + return undefined; + } + + return findKeyImpl(obj, predicate); +} + +function findKeyImpl>( + obj: T, + predicate: + | ((value: T[keyof T], key: keyof T, obj: T) => boolean) + | Partial + | [keyof T[keyof T], any] + | keyof T[keyof T] +) { + if (typeof predicate === 'function') { + return findKeyToolkit(obj, predicate); + } + + if (typeof predicate === 'object') { + if (Array.isArray(predicate)) { + const key = predicate[0]; + const value = predicate[1]; + + return findKeyToolkit(obj, matchesProperty(key, value)); + } + + return findKeyToolkit(obj, matches(predicate)); + } + + if (typeof predicate === 'string') { + return findKeyToolkit(obj, property(predicate)); + } +} From 09d3227d4577322fd5715a1d61687bb77c5bd7fe Mon Sep 17 00:00:00 2001 From: Sojin Park Date: Sun, 1 Dec 2024 21:18:36 +0900 Subject: [PATCH 2/2] Add docs --- docs/ja/reference/object/findKey.md | 50 ++++++++++++++++++++++-- docs/ko/reference/object/findKey.md | 43 ++++++++++++++++++++ docs/reference/object/findKey.md | 43 ++++++++++++++++++++ docs/zh_hans/reference/object/findKey.md | 43 ++++++++++++++++++++ src/compat/index.ts | 2 +- 5 files changed, 176 insertions(+), 5 deletions(-) diff --git a/docs/ja/reference/object/findKey.md b/docs/ja/reference/object/findKey.md index 6de0dafe6..e00d26e73 100644 --- a/docs/ja/reference/object/findKey.md +++ b/docs/ja/reference/object/findKey.md @@ -2,7 +2,7 @@ 提供されたテスト関数を満たすオブジェクト内の最初の要素のキーを検索します。 -## Signature +## インターフェース ```typescript function findKey>( @@ -11,16 +11,16 @@ function findKey>( ): keyof T | undefined; ``` -### Parameters +### パラメータ - `obj` (`T extends Record`): 検索するオブジェクト。 - `predicate` (`(value: T[keyof T], key: keyof T, obj: T) => boolean`): オブジェクト内の各値に対して実行する関数。 -### Returns +### 戻り値 (`keyof T | undefined`): 指定されたテスト関数を満たすオブジェクト内の最初の要素のキー。テストに合格する要素がない場合は未定義です。 -## Examples +## 例 ```typescript const users = { @@ -32,3 +32,45 @@ const users = { findKey(users, o => o.age < 40); // 'pebbles' findKey(users, o => o.age > 50); // undefined ``` + +## Lodash 互換性 + +`es-toolkit/compat` から `findKey` をインポートすると、Lodash と互換になります。 +キーを検索する条件を、さまざまな方法で指定できます。 + +- **検査関数**: 各要素に対して検査する関数を実行します。最初に `true` を返す要素のキーを返します。 +- **部分オブジェクト**: 指定されたオブジェクトと部分的に一致する要素のキーを返します。 +- **プロパティ-値ペア**: 指定されたプロパティと値が一致する要素のキーを返します。 +- **プロパティ名**: 指定されたプロパティに対して真と評価される要素のキーを返します。 + +### インターフェース + +```typescript +export function findKey>( + obj: T, + conditionToFind: (value: T[keyof T], key: keyof T, obj: T) => boolean +): keyof T | undefined; +export function findKey>(obj: T, objectToFind: Partial): keyof T | undefined; +export function findKey>( + obj: T, + propertyToFind: [keyof T[keyof T], any] +): keyof T | undefined; +export function findKey>(obj: T, propertyToFind: keyof T[keyof T]): keyof T | undefined; +``` + +### 例 + +```typescript +const users = { barney: { age: 36 }, fred: { age: 40 } }; + +findKey(users, o => o.age < 40); +// => 'barney' +findKey(users, { age: 36 }); +// => 'barney' +findKey(users, ['age', 36]); +// => 'barney' + +const languages = { javascript: { active: false }, typescript: { active: true } }; +findKey(users, 'active'); +// => 'typescript' +``` diff --git a/docs/ko/reference/object/findKey.md b/docs/ko/reference/object/findKey.md index 9eaab241d..f8ee6ff95 100644 --- a/docs/ko/reference/object/findKey.md +++ b/docs/ko/reference/object/findKey.md @@ -32,3 +32,46 @@ const users = { findKey(users, o => o.age < 40); // 'pebbles' findKey(users, o => o.age > 50); // undefined ``` + +## Lodash와 호환성 + +`es-toolkit/compat`에서 `findKey`를 가져오면 lodash와 완전히 호환돼요. + +키를 찾는 조건을 여러 방법으로 명시할 수 있어요. + +- **검사 함수**: 각각의 요소에 대해서 검사하는 함수를 실행해요. 처음으로 `true`를 반환하는 요소의 키를 반환해요. +- **부분 객체**: 주어진 객체와 부분적으로 일치하는 요소의 키를 반환해요. +- **프로퍼티-값 쌍**: 해당 프로퍼티와 값이 일치하는 요소의 키를 반환해요. +- **프로퍼티 이름**: 해당 프로퍼티에 대해서 참으로 평가되는 요소의 키를 반환해요. + +### 인터페이스 + +```typescript +export function findKey>( + obj: T, + conditionToFind: (value: T[keyof T], key: keyof T, obj: T) => boolean +): keyof T | undefined; +export function findKey>(obj: T, objectToFind: Partial): keyof T | undefined; +export function findKey>( + obj: T, + propertyToFind: [keyof T[keyof T], any] +): keyof T | undefined; +export function findKey>(obj: T, propertyToFind: keyof T[keyof T]): keyof T | undefined; +``` + +### 예시 + +```typescript +const users = { barney: { age: 36 }, fred: { age: 40 } }; + +findKey(users, o => o.age < 40); +// => 'barney' +findKey(users, { age: 36 }); +// => 'barney' +findKey(users, ['age', 36]); +// => 'barney' + +const languages = { javascript: { active: false }, typescript: { active: true } }; +findKey(users, 'active'); +// => 'typescript' +``` diff --git a/docs/reference/object/findKey.md b/docs/reference/object/findKey.md index 972f2a25f..e6f847166 100644 --- a/docs/reference/object/findKey.md +++ b/docs/reference/object/findKey.md @@ -32,3 +32,46 @@ const users = { findKey(users, o => o.age < 40); // 'pebbles' findKey(users, o => o.age > 50); // undefined ``` + +## Compatibility with Lodash + +Import `findKey` from `es-toolkit/compat` for full compatibility with lodash. + +You can specify the condition for finding keys in several ways: + +- **Predicate function**: You can provide a predicate function that will be applied to each value in the object. The function should return `true` for elements that match the criteria. The search continues until the predicate returns `true` for the first time. +- **Partial object**: You can also provide a partial object, and the function will return the key of the first element in the object that matches the properties of the provided object. +- **Property-value pair**: Alternatively, you can specify a property-value pair, where the function will return the key of the first element that has the specified property matching the given value. +- **Property name**: Lastly, you can provide a property name, and the function will return the key of the first element where the specified property has a truthy value. + +### Signature + +```typescript +export function findKey>( + obj: T, + conditionToFind: (value: T[keyof T], key: keyof T, obj: T) => boolean +): keyof T | undefined; +export function findKey>(obj: T, objectToFind: Partial): keyof T | undefined; +export function findKey>( + obj: T, + propertyToFind: [keyof T[keyof T], any] +): keyof T | undefined; +export function findKey>(obj: T, propertyToFind: keyof T[keyof T]): keyof T | undefined; +``` + +### Examples + +```typescript +const users = { barney: { age: 36 }, fred: { age: 40 } }; + +findKey(users, o => o.age < 40); +// => 'barney' +findKey(users, { age: 36 }); +// => 'barney' +findKey(users, ['age', 36]); +// => 'barney' + +const languages = { javascript: { active: false }, typescript: { active: true } }; +findKey(users, 'active'); +// => 'typescript' +``` diff --git a/docs/zh_hans/reference/object/findKey.md b/docs/zh_hans/reference/object/findKey.md index fd98cc684..dfd358a8a 100644 --- a/docs/zh_hans/reference/object/findKey.md +++ b/docs/zh_hans/reference/object/findKey.md @@ -32,3 +32,46 @@ const users = { findKey(users, o => o.age < 40); // 'pebbles' findKey(users, o => o.age > 50); // undefined ``` + +## Lodash 兼容性 + +从 `es-toolkit/compat` 导入 `findKey` 以实现与 lodash 的完全兼容。 + +您可以通过多种方式指定查找键的条件。 + +- **谓词函数**:对每个元素执行谓词函数。返回第一个返回 `true` 的元素的键。 +- **部分对象**:返回与给定对象部分匹配的元素的键。 +- **属性-值对**:返回具有指定属性和值匹配的元素的键。 +- **属性名**:返回指定属性评估为真的元素的键。 + +### 签名 + +```typescript +export function findKey>( + obj: T, + conditionToFind: (value: T[keyof T], key: keyof T, obj: T) => boolean +): keyof T | undefined; +export function findKey>(obj: T, objectToFind: Partial): keyof T | undefined; +export function findKey>( + obj: T, + propertyToFind: [keyof T[keyof T], any] +): keyof T | undefined; +export function findKey>(obj: T, propertyToFind: keyof T[keyof T]): keyof T | undefined; +``` + +### 示例 + +```typescript +const users = { barney: { age: 36 }, fred: { age: 40 } }; + +findKey(users, o => o.age < 40); +// => 'barney' +findKey(users, { age: 36 }); +// => 'barney' +findKey(users, ['age', 36]); +// => 'barney' + +const languages = { javascript: { active: false }, typescript: { active: true } }; +findKey(users, 'active'); +// => 'typescript' +``` diff --git a/src/compat/index.ts b/src/compat/index.ts index 1f7bd0d7d..a25a4b223 100644 --- a/src/compat/index.ts +++ b/src/compat/index.ts @@ -101,6 +101,7 @@ export { sumBy } from './math/sumBy.ts'; export { cloneDeep } from './object/cloneDeep.ts'; export { defaults } from './object/defaults.ts'; +export { findKey } from './object/findKey.ts'; export { fromPairs } from './object/fromPairs.ts'; export { get } from './object/get.ts'; export { has } from './object/has.ts'; @@ -115,7 +116,6 @@ export { property } from './object/property.ts'; export { set } from './object/set.ts'; export { toDefaulted } from './object/toDefaulted.ts'; export { unset } from './object/unset.ts'; -export { findKey } from './object/findKey.ts' export { conforms } from './predicate/conforms.ts'; export { conformsTo } from './predicate/conformsTo.ts';