Search code examples
typescriptunion-types

Using objects to discriminate union types


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);
}

Solution

  • 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';
    }