Reputation: 54
I am having trouble writing the correct types for a simple function.
I want to create a function that when given a key (keyof any
) or callback will return another function. This function takes in some data and returns another key.
In other words both toKey('propertyName')
and toKey(value => value.propertyName)
would return the function value => value.propertyName
. When called, the function would return the given value of said property only if the value is a string
, number
or symbol
type.
Here's an example:
function toKey(keyIdentity) { /* snippet */ }
interface IPerson {
firstName: string,
lastName: string,
age: number,
emails: string[]
};
const getKey1 = toKey((person: IPerson) => person.emails[0]);
const getKey2 = toKey('firstName');
const getKey3 = toKey('emails');
const person: IPerson = {
firstName: 'Craig',
lastName: 'Lipton',
age: 46,
emails: [
'<email-1>',
'<email-2>'
]
};
getKey1(person); // returns "<email-1>";
getKey2(person); // returns "Craig";
getKey3(person); // not allowed;
I've attempted to use generics and overloads to achieve the correct typing but it gets complicated very quickly.
function toKey(key?: null): () => void;
function toKey<K extends keyof any>(key: K): <U extends keyof any>(item: Record<K, U>) => U;
function toKey<T extends (...args: any[]) => keyof any>(key: T): T;
function toKey(key: any): any {
if (isNil(key)) {
return noop;
}
return isFunction(key) ? key : (value: T) => value[key];
}
Is there any simpler way to write this?
Upvotes: 2
Views: 1368
Reputation: 74500
If you want to be explicit in your types, the built-in PropertyKey
type can be used:
// type PropertyKey = string | number | symbol
function toKey<K extends PropertyKey>(key: K):
<T extends Record<K, PropertyKey>>(t: T) => PropertyKey
function toKey<T>(fn: (t: T) => PropertyKey): (t: T) => PropertyKey
function toKey(key: PropertyKey | ((t: Record<PropertyKey, PropertyKey>) => PropertyKey)) {...}
Test it:
toKey((person: IPerson) => person.emails[0])(person); // "<email-1>";
toKey('firstName')(person); // "Craig";
toKey('emails')(person); // error, emails prop is not string | number | symbol
toKey('lastName')({ firstName: "Lui" }); // error, object passed in has no key "lastName"
Upvotes: 1
Reputation: 39916
You have to use this,
function toKey<T, K extends keyof T = keyof T>(key: K): (v: T) => T[K];
const getKey3 = toKey<IPerson>('emails');
const getKey3 = toKey<IPerson,"emails">('emails');
Upvotes: 0