Home > Mobile >  TypeScript Interface typing with exhaustive enum check
TypeScript Interface typing with exhaustive enum check

Time:01-13

I have a enum and I let each value of the enum as a key in my interface whose value is specific to the key.

Example:

enum Fruits {
  Apple = "Apple",
  Banana = "Banana",
}

// EDIT: These slicers can have completely different shapes.
interface AppleSlicer { foo: () => string }
interface BananaSlicer { bar: () => number }

interface FruitSlicers {
  [Fruits.Apple]: AppleSlicer,
  [Fruits.Banana]: BananSlicer,
}

This works well, but I want have similar code in several places and I want them to give me compile errors when there's a new entry in the enum. Currently, this doesn't do any exhaustive check, so it does not. Is it possible to achieve that with TypeScript?

CodePudding user response:

You probably want to make use of the Record utility type, which works great with enums.

Something like:

enum Fruits {
  Apple = 'Apple',
  Banana = 'Banana',
}

type Slicer = Function; // Quick example case

type FruitSlicers = Record<Fruits, Slicer>; // <- Ensures every enum value is a key

const fruitSlicers: FruitSlicers = {
  [Fruits.Apple]: () => {},
  [Fruits.Banana]: () => {},
};

Now if I add another Fruit:

enum Fruits {
  Apple = 'Apple',
  Banana = 'Banana',
  Cherry = 'Cherry',
}

I get:

Property 'Cherry' is missing in type '{ Apple: () => void; Banana: () => void; }' but required in type 'FruitSlicers'

CodePudding user response:

For this you will need type compatibility checks. My recommended library for this is ts-expect: https://github.com/TypeStrong/ts-expect

Here is a complete sample (I've inlined expectType and TypeOf from ts-expect):

export const expectType = <Type>(value: Type): void => void 0;
export type TypeOf<Target, Value> = Exclude<Value, Target> extends never
  ? true
  : false;

type AppleSlicer = { apples: number }
type BananaSlicer = { bananas: number }

enum Fruits {
  Apple = "Apple",
  Banana = "Banana",
}

type FruitSlicersComplete = {
  [Fruits.Apple]: AppleSlicer,
  [Fruits.Banana]: BananaSlicer,
}
type FruitSlicersIncomplete = {
  [Fruits.Apple]: AppleSlicer,
  // [Fruits.Banana]: BananaSlicer,
}

type FruitSlicersExhaustive = {[key in Fruits]: any}

expectType<TypeOf<FruitSlicersExhaustive,FruitSlicersComplete>>(true); // Success ✅
expectType<TypeOf<FruitSlicersExhaustive,FruitSlicersIncomplete>>(true); // Error ❌
  •  Tags:  
  • Related