Vyhledávání na webu

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] ? [].