Home > Blockchain >  Typescript: Simplify typing of enum-like objects
Typescript: Simplify typing of enum-like objects

Time:07-09

in JavaScript I often (ab)use objects as pseudo-enums:

const application = {
    ELECTRIC: {propA: true, propB: 11, propC: "eee"},
    HYDRAULIC: {propA: false, propB: 59, propC: "hhh"},
    PNEUMATIC: {propA: true, propB: 87, propC: "ppp"},
}

const foo = application.ELECTRIC
const bar = application.HYDRAULIC

I'd like to achieve a similar behavior in TypeScript, but in a type-safe manner. Here's what I have so far:

type Application = "ELECTRIC" | "HYDRAULIC" | "PNEUMATIC";

interface ApplicationProps {
  propA: boolean;
  propB: number;
  propC: string;
}

const application: Record<Application, ApplicationProps> = {
    ELECTRIC: {propA: true, propB: 11, propC: "eee"},
    HYDRAULIC: {propA: false, propB: 59, propC: "hhh"},
    PNEUMATIC: {propA: true, propB: 87, propC: "ppp"},
}

const foo = application.ELECTRIC;
---
let bar: ApplicationProps; // an attempt to restrict bar to application.ELECTRIC|application.HYDRAULIC|application.PNEUMATIC
bar = application.HYDRAULIC;

That works okay but feels clumsy.

1) There seems to be lot of repetition in type/interface/object

Is there some TS-Trickery to automatically infer type Application / interface ApplicationProps from the object? Like some keyof typeof something magic?

2) It feels strange to type bar as ApplicationProps when I actually want to restrict it to members of application

Is there a more elegant way to restrict bar to application.ELECTRIC|application.HYDRAULIC|...?

3) If I want to define bar in another file, I need to import ApplicationProps and application. I know the type info is part of application but I don't know how to get it.

The best I got so far was: let bar: typeof application[keyof typeof application] Which of course is stupid. There has to be a better way?!



Thanks in advance :)

CodePudding user response:

So you can do something like this:


// all application keys will be defined here
// as const is needed here so typescript won't start widening the type to string[]
const applications = ["ELECTRIC", "HYDRAULIC", "PNEUMATIC"] as const

// get all keys as a Union Type
type ApplicationKeys = typeof applications[number]
// create your enum object as a type
type Application = { [Key in ApplicationKeys]: ApplicationProps<Key> }
// define your props with a key so typescript can differentiate between your enums
type ApplicationProps<T> = {
  key: T,
  propA: boolean;
  propB: number;
  propC: string;
}
// the actual enum definition
const application: Application = {
  ELECTRIC: { key: "ELECTRIC", propA: true, propB: 11, propC: "eee" },
  HYDRAULIC: { key: "HYDRAULIC", propA: false, propB: 59, propC: "hhh" },
  PNEUMATIC: { key: "PNEUMATIC", propA: true, propB: 87, propC: "ppp" },
}
// test
let foo = application.ELECTRIC;
let bar: Application["PNEUMATIC"]; 
bar = application.HYDRAULIC; // error: Type 'ApplicationProps<"HYDRAULIC">' is not assignable to type 'ApplicationProps<"ELECTRIC">'. 
bar ===foo // error his condition will always return 'false' since the types have no overlap

The neat part is that if you want to add a new application key you just have to add it to the list and typescript will throw errors for the missing or unhandled key in a switch or if statement(exhaustive switch block)[https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript].

I hope you understand my bad english :) CODE

CodePudding user response:

I sometimes do something like this, with having a enum list mapping to specific types.

But I'm also curious about how this could be done better.

enum ApplicationType {
  ELECTRIC = 'ELECTRIC',
  HYDRAULIC = 'HYDRAULIC',
  PNEUMATIC = 'PNEUMATIC',
}

interface Application {
  propA: boolean;
  propB: number;
  propC: string;
}

interface ElectricApplication extends Application {
  propD: string;
}
interface PneumaticApplication extends Application {}
interface HydaulicApplication extends Application {}

type ApplicationTypeMap = {
  [ApplicationType.ELECTRIC]: ElectricApplication;
  [ApplicationType.HYDRAULIC]: HydaulicApplication;
  [ApplicationType.PNEUMATIC]: PneumaticApplication;
};

const applications: { [key in ApplicationType]: ApplicationTypeMap[key] } = {
  ELECTRIC: {
    propA: true,
    propB: 11,
    propC: 'eee',
    propD: 'boo',
  },
  HYDRAULIC: { propA: false, propB: 59, propC: 'hhh' },
  PNEUMATIC: { propA: true, propB: 87, propC: 'ppp' },
};

const foo = applications[ApplicationType.ELECTRIC];
console.log(foo.propD);

const bar = applications[ApplicationType.HYDRAULIC];
console.log(bar.propA);
  • Related