I have a problem with typings in my code - union type constraint is generated instead of picking only one type from ItemProperties types dictionary. Is there any solution to make strict constraint for props property inside ItemConfig based on current ItemType?
P. S. wrapping T generic with tuple at props type declaration (as said here) not solves the problem.
Simplified code example:
enum ItemType {
Dog = 1,
Car = 2,
Building = 3,
}
interface ItemProperties {
[Item.Dog]: {
name: string;
};
[Item.Car]: {
power: number;
};
}
interface ItemConfig<T extends ItemType = ItemType> {
type: T;
props: T extends keyof ItemProperties ? ItemProperties[T] : {};
}
const config: ItemConfig[] = [
{
type: ItemType.Dog,
props: ...
}
];
Expected typeof props:
{ name: string }
Actual typeof props:
{ name: string } | { power: number } | {}
As remarked in the comments, the issue is that
ItemConfigis equal to{type: ItemType, props: {} | {name: string} | {power: number}}whereas you'll want it to be a discriminating union{type: ItemType.Dog, props: {name: string}} | {type: ItemType.Car, props: {power: number}} | ..to type the array elementpropscorrectly.One way to create this union is by using a distributive conditional type (docs):
TypeScript playground
Because the
Tin the condition is a naked type parameter,ItemConfig<ItemType.Dog | ItemType.Car | ItemType.Building>distributes toItemConfig<ItemType.Dog> | ItemConfig<ItemType.Car> | ItemConfig<ItemType.Building>, which is the desired union.Alternatively (as captain-yossarian remarked), since
ItemTypeextendsPropertyKey(i.e.string | number | symbol), you can use a mapped type to create an object that has the constituents of the desired union as its values, and index that object to obtain the union:TypeScript playground
This has the advantage that you don't need
ItemConfigto have a generic parameterT, but it is limited to types that extendPropertyKey(otherwise you can't use it as a key in the mapped type).Both approaches approaches yield the same
ItemConfigunion, which will allow the appropriatepropstype to be inferred for each array element: