Search code examples
typescriptgenericstypestypeguards

How to infer type from types map


I can't figure out how to ensure type-safety in the onEvent function.

enum MyEnum {
  One,
  Two
}

type Payloads = {
  [MyEnum.One]: { iAmOne: string; one: number };
  [MyEnum.Two]: { iAmTwo: string; two: number };
};

interface BetEvent<ENUM extends MyEnum> {
  type: ENUM;
  payload: Payloads[ENUM];
}

const onEvent = (ev: BetEvent<any>) => {
  if (ev.type === MyEnum.Two) {
    ev.type; // should be MyEnum.Two
    ev.payload; // should be { iAmTwo: string; two: number };
    ev.payload.iAmOne; // should throw Error
  }
};

Solution

  • Here you go...

    enum BetEventType {
      One,
      Two
    }
    
    type BetPayloads = {
      [BetEventType.One]: { iAmOne: string; one: number };
      [BetEventType.Two]: { iAmTwo: string; two: number };
    };
    
    type BetEvent = {
      [T in BetEventType]: {
        type: T,
        payload: BetPayloads[T]
      }
    }[BetEventType]
    
    const onEvent = (ev: BetEvent) => {
      if (ev.type === BetEventType.Two) {
        ev.type; // is BetEventType.Two
        ev.payload; // is { iAmTwo: string; two: number };
        ev.payload.iAmOne; // gives compile error
      }
    };
    

    Demo

    I know there is an accepted answer but this one is undoubtedly better because...

    1. No runtime overhead (meaning no addition in compiled code which is going to be executed)
    2. Better BetEvent type because it is nice union of all possible event types (hover to see what I'm talking about)

    You should consider marking this as accepted