As remarked in the comments, the issue is that ItemConfig is 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 element props correctly.
One way to create this union is by using a distributive conditional type (docs):
type ItemConfig<T = ItemType> = T extends ItemType ? {
type: T;
props: T extends keyof ItemProperties ? ItemProperties[T] : {};
} : never
TypeScript playground
Because the T in the condition is a naked type parameter, ItemConfig<ItemType.Dog | ItemType.Car | ItemType.Building> distributes to ItemConfig<ItemType.Dog> | ItemConfig<ItemType.Car> | ItemConfig<ItemType.Building>, which is the desired union.
Alternatively (as captain-yossarian remarked), since ItemType extends PropertyKey (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:
type ItemConfig = {
[T in ItemType]: {
type: T,
props: T extends keyof ItemProperties ? ItemProperties[T] : {}
}
}[ItemType]
TypeScript playground
This has the advantage that you don't need ItemConfig to have a generic parameter T, but it is limited to types that extend PropertyKey (otherwise you can't use it as a key in the mapped type).
Both approaches approaches yield the same ItemConfig union, which will allow the appropriate props type to be inferred for each array element:
const config: ItemConfig[] = [
{
type: ItemType.Dog,
props: {name: 'x'}
// type of props: {name: string}
},
{
type: ItemType.Car,
props: {power: 7}
// type of props: {power: number}
},
{
type: ItemType.Dog,
props: {power: 7} // Type error
// type of props: {name: string}
}
];