I am building a function which accepts an interface and I want this to infer the return types from the 'key' in my options object passed as a function param
My desired results would be something like this reduced code example:
const stringKeyValue = createKeyValueMapped<{a: string, b: number}>({
key: "a", // key: "a"
value: "hi", // value: string
transform: (value) => value ' more text', // (value: string) => string
});
stringKeyValue.key; // key: 'a'
stringKeyValue.value; // value: string
But with my current code the key
type is 'a' | 'b'
and the value type is 'string' | 'number'
.
My current broken solution is:
export type KeyValue<
Inputs,
Key extends keyof Inputs,
Value = Inputs[Key]
> = {
key: Key;
value: Value;
transform: (value: Value) => Value;
};
type KeyValuesMapped<Inputs> = {
[Key in keyof Inputs]: KeyValue<Inputs, Key>;
}[keyof Inputs];
const createKeyValueMapped = <Inputs,>({ key, value }: KeyValuesMapped<Inputs>) =>
({
key,
value,
});
I have tried to create a type for KeyValue
which includes the options inside a function and type the returns but the completely breaks the mapping.
Fuller example is in this Link to Playground
CodePudding user response:
Currently, the key
property is not generic. The function does not know or care which value you pass to it. The output type can therefore not change based on the key
.
The "easy" solution would be to add a generic type for key
.
const createKeyValueMapped = <
Inputs,
K extends keyof Inputs
>({ key, value }: KeyValue<Inputs, K>) =>
({
key,
value,
});
The caller would have to specify the key explicitly when calling the function since TypeScript does not support partial type inference yet.
const stringKeyValue = createKeyValueMapped<CustomInputs, "a">({
key: "a",
value: "hi",
transform: (value) => value ' more text',
});
If we want to avoid explicitly setting the second generic type, we can use currying.
const createKeyValueMapped = <Inputs,>() =>
<K extends keyof Inputs>({key, value}: KeyValue<Inputs, K>) => ({
key,
value,
});
const stringKeyValue = createKeyValueMapped<CustomInputs>()({
key: "a",
value: "hi",
transform: (value) => value ' more text',
});
The outer function takes an explicit generic type while the inner function infers the key
.
stringKeyValue.key; // "a"
stringKeyValue.value; // string