Given the following type:
export type MyType = {
a: boolean;
b: number;
}
I want to create a type:
export type Mapper<T> = {
key: keyof T;
func: (value: string) => T[key];
}
So that I can have:
const mapToMyTypePropertyA: Mapper<MyType> = {
key: "a",
func: v => !!v // enforce output type to be the type of MyType["a"], which is boolean
};
const mapToMyTypePropertyA: Mapper<MyType> = {
key: "b",
func: v => v.length // enforce output type to be the type of MyType["b"], which is number
};
i.e. I want to enforce the return type of func
(which is a function) to be the type of a property of a generic type determined by the key
(which is a keyof the generic type).
Is this possible?
I could only get to this so far:
export type Mapper<T, K extends keyof T> = {
key: K;
func: (value: string) => T[K];
}
const mapToMyTypePropertyA: Mapper<MyType, "a"> = {
key: "a",
func: v => !!v // I get the enforcement here
}
But this requires repeating the key twice in the generic type and in the property key
.
CodePudding user response:
You can use another type to "generate" the possible objects:
type GenerateCombos<T> = {
[K in keyof T]: {
key: K;
func: (value: string) => T[K];
};
}[keyof T];
This will give us a union of the allowed objects. For example, GenerateCombos<{ foo: string }>
would only give us:
{ key: "foo"; func: (value: string) => string }
And GenerateCombos<MyType>
gives us the nice type:
{
key: "a";
func: (value: string) => boolean;
} | {
key: "b";
func: (value: string) => number;
}
To use it, you can give it an alias first:
type Mapper = GenerateCombos<MyType>;
Then annotate the types of variables:
const mapToMyTypeProperty1: Mapper = {
key: "a",
func: v => !!v // enforce output type to be the type of MyType["a"], which is boolean
};
const mapToMyTypeProperty2: Mapper = {
key: "b",
func: v => v.length // enforce output type to be the type of MyType["b"], which is number
};
And it also errors if you do it incorrectly:
const mapToMyTypeProperty3: Mapper = {
key: "a",
func: v => v // ! error
};
const mapToMyTypeProperty4: Mapper = {
key: "b",
func: v => !!v // ! error
};
Try this out here.
CodePudding user response:
Another solution is to use a utility function to make TypeScript try to infer the generics for us:
function mapper<K extends keyof MyType>(mapper: Mapper<MyType, K>) { return mapper; }
It's very easy to use, just call the function with the object as you would normally:
const mapToMyTypeProperty1 = mapper({
key: "a",
func: v => !!v // enforce output type to be the type of MyType["a"], which is boolean
});
const mapToMyTypeProperty2 = mapper({
key: "b",
func: v => v.length // enforce output type to be the type of MyType["b"], which is number
});
And it errors if you do it incorrectly, too:
const mapToMyTypeProperty3 = mapper({
key: "a",
func: v => v // ! WRONG TYPE
});
const mapToMyTypeProperty4 = mapper({
key: "b",
func: v => !!v // ! WRONG TYPE
});
You can play around with this more here