简体   繁体   中英

Mapped object type with generic per key in TypeScript

Let's say I have a type that is any array of two items: a list of parameters and a function with arguments that matches the parameters. To make it easy, I've created a function that can infer the generic type.

type MapParamsToFunction<A extends any[]> = [[...params: A], (...args: A) => void]

function asMap<A extends any[]>(map: MapParamsToFunction<A>): MapParamsToFunction<A> {
    return map;
}

asMap([[],() => {}]) // good
asMap([[0, "Hello World", 2], (a: number, b: string, c: number) => { }]); // good
asMap([[0, "Hello World", 2], (a: number, b: number, c: number) => { }]); // error
asMap([[0, "Hello World"], (a: number, b: string, c: number) => { }]); // error

No problem so far. Now, I want to take this and create a dictionary where each key can have a different list of parameters/arguments. However, I don't see a way to have TypeScript use a different generic on each key.

I've tried using any[] on the type, but it will not throw a type error if the parameters don't match the arguments.

type FunctionDictionary<T extends string> = {
    [K in T]: MapParamsToFunction<any[]>
}

function asDictionary<T extends string>(dict: FunctionDictionary<T>): FunctionDictionary<T> {
    return dict;
}

let obj = asDictionary({
    "foo": [[0, "Hello World", 2], (a: number, b: number, c: number) => { }], // no type error
    "bar": [["","",""], (a: string, b: string, c: string) => { }]
});

Is there a way to map this, so each argument can have it's own generic parameter list?

type FunctionDictionary<T extends string> = {
    [K in T]: MapParamsToFunction<?> // <--- what goes here
}

TypeScript doesn't have existential types to say "a MapParamsToFunction<X> for some type X ", so you can't just make FunctionDictionary a record of those. Luckily, it does allow inference from mapped types , so you can take an object type T whose properties are all argument lists, and map each property K to an appropriate MapParamsToFunction<T[K]> :

type FunctionDictionary<T extends Record<keyof T, any[]>> = {
    [K in keyof T]: MapParamsToFunction<T[K]>
}

Then your asDictionary() will infer the T object from a passed-in FunctionDictionary<T> like this:

function asDictionary<T extends Record<keyof T, any[]>>(
    dict: FunctionDictionary<T>
): FunctionDictionary<T> {
    return dict;
}

And you get the behavior you want:

let obj = asDictionary({
    "foo": [[0, "Hello World", 2], (a: number, b: number, c: number) => { }], // error!
    // --------------------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Types of parameters 'b' and 'args_1' are incompatible.
    "bar": [["", "", ""], (a: string, b: string, c: string) => { }]
});

Playground link to code

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM