I am trying to create union types in TypeScript where the discriminator is a value on a nested object, rather than a primitive value directly on the type. I am working with objects returned from an API and therefore cannot alter the data structures being returned.
Is this possible in TypeScript, without needing to resort to user-defined type guards?
Here is a simplified example:
type SquareIdentifier = {
type: "square";
};
type CircleIdentifier = {
type: "circle";
};
type SquareShape = {
identifier: SquareIdentifier;
sideLength: number;
};
type CircleShape = {
identifier: CircleIdentifier;
diameter: number;
};
type Shape = SquareShape | CircleShape;
// assume we have been given a variable `x` of Shape
const x = ({} as any) as Shape;
// at this point, x.identifier is considered a SquareIdentifier | CircleIdentifier
if (x.identifier.type === "square") {
// at this point, x.identifier is considered a SquareIdentifier
// however, x is still considered a Shape, not a SquareShape, which is the only Shape to contain a ShapeIdentifier
// error here is:
// "Property 'sideLength' does not exist on type 'Shape'"
// "Property 'sideLength' does not exist on type 'CircleShape'"
console.log(x.sideLength);
}
Yes, it's possible. You need to use user defined type guards
For example like this:
if (isSquareShape(x)) {
console.log(x.sideLength);
}
function isSquareShape(value: Shape): value is SquareShape {
return value.identifier.type === 'square';
}