What is a mechanism I can use in TS to have TS do type inference with recursive discriminated unions?
Given the example below:
interface Circle {
type: 'circle';
radius: 3;
}
interface Square {
type: 'square';
sides: 4;
}
interface Bag {
type: 'bag';
color: 'green';
children: AnyItem[];
}
type AnyItem = Circle | Square | Bag;
interface CircleConstructorParams {
type: 'circle';
}
interface SquareConstructorParams {
type: 'square';
}
interface BagConstructorParams {
type: 'bag';
children: AnyConstructorParams[];
}
type AnyConstructorParams = CircleConstructorParams | SquareConstructorParams | BagConstructorParams;
function getItem(itemConstructorParams: AnyConstructorParams) {
if (itemConstructorParams.type === 'circle') {
const circle: Circle = {
type: 'circle',
radius: 3,
};
return circle;
} else if (itemConstructorParams.type === 'square') {
const square: Square = {
type: 'square',
sides: 4,
};
return square;
} else if (itemConstructorParams.type === 'bag') {
const bag: Bag = {
type: 'bag',
color: 'green',
children: itemConstructorParams.children.map(child => {
return getItem(child);
})
};
return bag;
}
throw new Error('Invalid');
}
const circle = getItem({ type: 'circle' });
console.log(circle.radius);
const square = getItem({ type: 'square' });
console.log(square.sides);
const nested = getItem({
type: 'bag',
children: [
{ type: 'square', },
{ type: 'circle', },
{
type: 'bag',
children: [
{ type: 'square' },
{ type: 'circle' },
]
},
],
});
console.log(nested.children[2].children[1].radius);
I receive the following errors:
Property 'radius' does not exist on type 'Circle | Square | Bag'.
Property 'radius' does not exist on type 'Square'.
Property 'sides' does not exist on type 'Circle | Square | Bag'.
Property 'sides' does not exist on type 'Circle'.
Property 'children' does not exist on type 'Circle | Square | Bag'.
Property 'children' does not exist on type 'Circle'.
Given that I've used literal members, type guards and typed returned objects, I'd expect TS to be able to infer the type of each nested "Item", from each nested "Item constructor".
What is the reason for TS being unable to infer types when used in this way, and what is the correct way to do this, whilst avoiding doing the following:
console.log((((nested as Bag).children[2] as Bag).children[1] as Circle).radius);