I'm trying to create a simple switch function which takes a first parameter that must be an union of string & an object which have keys based on the first parameter union and can return any value.
export const mySwitch = <T extends string>(value: T, possibilities: {[key in T]: any}): any => {
return possibilities[value];
};
Typical usage would be
let option: "val1" | "val2" | "val3" = "val1";
// should returns s1
// Impossible should be type-checked as an error since it's not part of the option union type
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"});
My problem occurs because the generic type T
must be a string
in order to be used as an object key. I don't know how you can tell T
to be an union of string
.
I tried T extends string
with no success.
The T extends string
version seems to work well. It disallows impossible
, but wouldn't you want to disallow it since if the parameter can never have that value that option would be useless?:
export const mySwitch = <T extends string>(value: T, possibilities: {[key in T]: any}): any => {
return possibilities[value];
};
declare let option: "val1" | "val2" | "val3";
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"});
play
If you want to allow the extra keys you could declare the case object separately (bypassing excess property checks and allowing you to reuse the case object)
declare let option: "val1" | "val2" | "val3";
const casses = {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"}
mySwitch(option, casses);
play
Or you could change your type a little bit so the generic type parameter is the case object, and the value will the be typed as keyof T
:
export const mySwitch = <T>(value: keyof T, possibilities: T): any => {
return possibilities[value];
};
declare let option: "val1" | "val2" | "val3";
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "impossible"});
play
Also a better option would be to preserve the type from the case object instead of using any
:
export const mySwitch = <T, K extends keyof T>(value: K, possibilities: T): T[K] => {
return possibilities[value];
};
declare let option: "val1" | "val2" | "val3";
mySwitch(option, {val1: 1, val2: "s2", val3: "s3", impossible: false}); // returns string | number
play
Edit:
To preserve both correct return type and error if there are possibilities not present in union you could use this:
const mySwitch = <T extends Record<K, any>, K extends string>(value: K, possibilities: T & Record<Exclude<keyof T, K>, never>): any => {
return possibilities[value];
};
let option: "val1" | "val2" | "val3" = (["val1", "val2", "val3"] as const)[Math.round(Math.random() * 2)]
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3" });
mySwitch(option, {val1: "s1", val2: "s2", val3: "s3", impossible: "" }); //err on impossible
play
Note that because typescript does control flow analysis you need to make sure option
is not just types as the actual constant you assign instead of the type annotation you specify
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With