ken
ken

Reputation: 8993

Interface equivalent for a union type

I have a Typescript type defined as:

export type IStepFunctionOperand =
  | IStepFunctionOperand_StringEquals
  | IStepFunctionOperand_StringGreaterThan
  | IStepFunctionOperand_StringGreaterThanEquals
  | IStepFunctionOperand_StringLessThan
  | IStepFunctionOperand_StringLessThanEquals
  | IStepFunctionOperand_NumericEquals
  | IStepFunctionOperand_NumericGreaterThan
  | IStepFunctionOperand_NumericGreaterThanEquals
  | IStepFunctionOperand_NumericLessThan
  | IStepFunctionOperand_NumericLessThanEquals;

Where each of the conditionals looks something like the following:

export interface IStepFunctionOperand_NumericLessThanEquals
  extends IStepFunctionBaseLogicalOperand {
  /** compare the value passed in -- and scoped by "Variable" -- to be numerically equal to a stated number */
  NumericLessThanEquals?: number;
}

export interface IStepFunctionBaseLogicalOperand {
  /** points to the specific area of context which is being evaluated in the choice */
  Variable: string;
}

This does what I want it to as a type but it would be super convenient to have this type be made into an interface. If I were able to do this I could then define an interface like so:

export interface IStepFunctionChoiceItem<T> extends Partial<IStepFunctionOperand> {
  // simple operands leverage inheritance but are optional

  // complex operators
  And?: IStepFunctionOperand[];
  Or?: IStepFunctionOperand[];
  Not?: IStepFunctionOperand;

  // State machine
  Next?: keyof T;
  End?: true;
}

Is this possible?

Upvotes: 2

Views: 99

Answers (1)

Aluan Haddad
Aluan Haddad

Reputation: 31803

While you cannot use an interface to represent a union, for example to extend from it, you can use intersection types with type aliases to reuse or parameterize a union type

From your example:

export type StepFunctionChoiceItem<T> =
  & Partial<StepFunctionOperand>
  & {
  // simple operands leverage inheritance but are optional

  // complex operators
  and?: StepFunctionOperand[];
  or?: StepFunctionOperand[];
  not?: StepFunctionOperand;

  // State machine
  next?: keyof T;
  end?: true;
};

export type StepFunctionOperand =
  | StepFunctionOperand_StringEquals
  | StepFunctionOperand_StringGreaterThan
  | StepFunctionOperand_StringGreaterThanEquals
  | StepFunctionOperand_StringLessThan
  | StepFunctionOperand_StringLessThanEquals
  | StepFunctionOperand_NumericEquals
  | StepFunctionOperand_NumericGreaterThan
  | StepFunctionOperand_NumericGreaterThanEquals
  | StepFunctionOperand_NumericLessThan
  | StepFunctionOperand_NumericLessThanEquals;

This isn't the same as interface extension because, among other differences, members with. matching names are neither overridden nor overloaded and instead are themselves intersected. However, intersection types will get you the essentially desired behavior.

Upvotes: 1

Related Questions