Existuje způsob, jak v TypeScriptu získat vnořené typy z objektu na základě řetězců v poli?
Snažím se vytvořit typovanou komparátorovou funkci s typovou bezpečností, abych si byl jistý, že vždy třídím podle existujících hodnot. Zatím jsem udělal následující:
export enum SortDirection {
ASCENDING = "ASCENDING",
DESCENDING = "DESCENDING",
}
/**
* Sorts in place Array of objects
* @param path path to numeric value
* @param direction the way that numeric values will be evaluated
* @returns the sorted array
*/
export const sortByNumeric = <T>(path: Array<keyof T>, direction = SortDirection.ASCENDING) => <K extends Record<keyof T, any>>(current: K, next: K) => {
const extractedA = +path.reduce((acc, k) => acc[k], current);
const extractedB = +path.reduce((acc, k) => acc[k], next);
return direction === SortDirection.ASCENDING ? extractedA - extractedB : extractedB - extractedA;
}
let source = [
{
name: 'Jon',
age: 20,
father: {
name: 'Jon Senior',
age: 60,
otherProp: 100
}
},
{
name: 'Gabriel',
age: 25,
father: {
name: 'Jon Senior',
age: 80,
otherProp: 20
}
}
];
source.sort(sortByNumeric(['age']));
Funkce funguje správně s jedním vlastností, ale není schopna pracovat s vnořenými cestami, jako je tato:
source.sort(sortByNumeric(['father' , 'otherProp']));
// Here this will throw an error that the prop doesn't exits, while this is actually a valid property
nebo
source.sort(sortByNumeric(['age' , 'father']));
// Here there will be no error, while at the same time this is invalid value as there is no key for the 'father'
Jsem si jistý, že s generiky dělám něco špatně, ale nemám tušení, jak a co zde opravit, jakákoli rada bude oceněna.
Odpověď č. 1
Je to možné, ale není jednoduché zadávat libovolně zanořené klíče. Záleží na tom, jak přesně chcete, aby se typ choval, ale možná implementace takového typu by mohla být:
export type NumericKeyPath<T> =
[T] extends [number] ? []
: [T] extends [object] ? NumericKeyPathProperty<T, keyof T>
: never;
type NumericKeyPathProperty<T, Key extends keyof T> =
Key extends (string | number) ? [ Key, ...NumericKeyPath<T[Key]> ]
: never;
type Test = NumericKeyPath<{ name: string; age: number; father: { name: string; age: number; otherProp: number } }>;
// type Test = ["age"] | ["father", "age"] | ["father", "otherProp"]
Použito s vaší komparační funkcí:
export const sortByNumeric = <T>(path: NumericKeyPath<T>, direction = SortDirection.ASCENDING) => (current: T, next: T) => {
// the below casts could be done more precisely
const extractedA = +(path as string[]).reduce((acc: any, k) => acc[k], current);
const extractedB = +(path as string[]).reduce((acc: any, k) => acc[k], next);
return direction === SortDirection.ASCENDING ? extractedA - extractedB : extractedB - extractedA;
}
let source = [
{
name: 'Jon',
age: 20,
father: {
name: 'Jon Senior',
age: 60,
otherProp: 100
}
},
{
name: 'Gabriel',
age: 25,
father: {
name: 'Jon Senior',
age: 80,
otherProp: 20
}
}
];
source.sort(sortByNumeric(['age']));
// Works
source.sort(sortByNumeric(['father' , 'otherProp']));
// Works
source.sort(sortByNumeric(['age' , 'father']));
// Error
Odkaz na hřiště
Všimněte si, že protože se zdálo, že to byla záměrem funkce, napsal jsem NumericKeyPath tak, aby povoloval pouze cesty k číselným hodnotám. Příslušný řádek je [T] extends [number] ? [], například pokud byste chtěli povolit také řetězcové hodnoty, mohli byste to změnit na [T] extends [number | string] ? [].
