I'm trying to make a function that can take a generic objects key and use that to create a function to sort string properties using String.localCompare
but I can't get TypeScript to understand that a[key]
and b[key]
are supposed to be strings.
How do I enforce this in TS?
const makeStringSort = <T>(key: keyof T) => {
return (a: T, b: T) => {
return a[key].toLowerCase().localeCompare(b[key].toLowerCase());
};
};
Property 'toLowerCase' does not exist on type 'T[keyof T]'
// example usage
interface Item {
prop: string;
other: number;
}
const sortFn = makeStringSort<Item>("prop");
const items: Item[] = [];
items.sort(sortFn);
CodePudding user response:
Updated answer: use mapped types
const makeStringSort = <T extends string>(key: T) => {
return <
U extends {
[Property in T]: string;
}
>(
a: U,
b: U
) => {
return a[key].toLowerCase().localeCompare(b[key].toLowerCase());
};
};
CodePudding user response:
Reading your code seems that key is a subtype of string and a
and b
are Record<string, string>
.
const makeStringSort = <T extends Record<string, string>>(key: keyof T) => {
return (a: T, b: T) => {
return a[key].toLowerCase().localeCompare(b[key].toLowerCase());
};
};
CodePudding user response:
If you define a helper:
type KeysOfType<O, T> = {
[K in keyof O]: O[K] extends T ? K : never;
}[keyof O];
to extract the keys of O
that have a value of type T
, then you can use it to constrain your function:
const makeStringSort = <T>(key: KeysOfType<T, string>) => {
return <TV extends { [K in KeysOfType<T, string>]: string }>(a: TV, b: TV) => {
return a[key].toLowerCase().localeCompare(b[key].toLowerCase());
};
};