Takeshi Tokugawa YD
Takeshi Tokugawa YD

Reputation: 923

Refer the keys of one enum to keys or values of another enum

In below code, Keys of PropertiesNamesInDataBase must be even with User.Keys. The values of PropertiesNamesInDataBase, as is obviously from name, are being using in backend, but names in frontend must be unified.

namespace User {
    export enum Keys {
        ID = "ID",
        name = "name"
    }
}

enum PropertiesNamesInDataBase {
    ID = "id",
    name = "nm"
}

At least two problems here:

  1. We need to re-type keys or copy-paste them
  2. PropertiesNamesInDataBase is fully independent on User.Keys, however conceptually PropertiesNamesInDataBase's keys must refer to User.Keys by some way.

Here is attempt to solve the second problem by referencing of PropertiesNamesInDataBase keys to User.Keys:

namespace User {
    export enum Keys {
        ID = "ID",
        name = "name"
    }
}

enum PropertiesNamesInDataBase {
    [User.Keys.ID] = "id",
    [User.Keys.name] = "nm"
}

However it's impossible in TypeScript:

Computed property names are not allowed in enums. (1164)

Please ever tech me how to reuse enum keys, or how to refer PropertiesNamesInDataBase's keys on User.Keys's values.

Upvotes: 1

Views: 1795

Answers (1)

jcalz
jcalz

Reputation: 328433

I'd be inclined to forget about enum entirely and instead build up your own enum-like objects. This will give you the flexibility you need to write the keys only once, but you will lose some of the built-in expressiveness that comes with enum (namely that enum Foo {...} creates both an object named Foo and a type named Foo. If you create const Foo = ... instead, you only get the object, and will need to define your own type yourself if you want it).

Here's one approach:

const KeysAndPropNames = {
  ID: { key: "ID", propName: "id" },
  name: { key: "name", propName: "nm" }
} as const;

You've got just one object which is a mapping from key to the values you had in your old User.Keys and PropertiesNamesInDataBase enums. You can extract those enum-like objects yourself like this:

const User = { Keys: objMapProp(KeysAndPropNames, "key") };
const PropertiesNamesInDataBase = objMapProp(KeysAndPropNames, "propName");

where objMapProp() is a function you can put in a library that maps a property access over an object:

// library
function objMapProp<T extends Record<keyof T, Record<K, any>>, K extends keyof any>(
  obj: T,
  key: K
) {
  const ret = {} as { [P in keyof T]: T[P][K] };
  for (let k in obj) {
    ret[k] = obj[k][key];
  }
  return ret;
}

If you examine the type of the new User and PropertiesNamesInDataBase objects with IntelliSense, you'll see they match up to the old values:

/* const User: {
    Keys: {
        readonly ID: "ID";
        readonly name: "name";
    };
} */

/* const PropertiesNamesInDataBase: {
    readonly ID: "id";
    readonly name: "nm";
} */

If you want the types named User.Keys and PropertiesNamesInDataBase, you can make them as the union of all the value types of the corresponding objects:

namespace User {
  export type Keys = (typeof User.Keys)[keyof typeof User.Keys];
  // type User.Keys = "ID" | "name"
}
type PropertiesNamesInDataBase = typeof PropertiesNamesInDataBase[keyof typeof PropertiesNamesInDataBase];
// type PropertiesNamesInDataBase = "id" | "nm"

Anyway, hope that either meets your needs or gives you some idea how to proceed. Good luck!

Playground link to code

Upvotes: 1

Related Questions