user2789284
user2789284

Reputation: 771

Looping through enums and populating a Typescript Record where keys are enums

I like the type-safety bit of using a Record in Typescript but seem to be in a bind with respect to looping through keys enums and populating the record

export enum Key {
  A = 'A',
  B = 'B',
  C = 'C'
}

export interface Value {
  isAvailable: boolean;
  reasons: string[];
}

export type Access = Record<Key, Value>;

export function access() {
    // I would like to avoid this initialization but TS does not allow it because it defeats the 
    // purpose and makes me initialize a value for each key in the enum upfront.
    const featureAccess: Access = {
      [Key.A]: null,
      [Key.B]: null,
      [Key.C]: null,
    };
    Object.keys(Key).forEach((eachKey: string) => {
      const feature = Access[eachKey];
      featureAccess[feature] = {
        isAvailable: ..., // Populate from API
        reasons: ...// Populate from API
      };
    });
    return featureAccess;
}

Is this a wrong candidate for using the Typescript Record?

Upvotes: 2

Views: 2856

Answers (3)

M&#39;sieur Toph&#39;
M&#39;sieur Toph&#39;

Reputation: 2676

My answer comes very late after the OP asked the question.

I was facing the same issue and I must admit the @Johannes Brodwall answer is very cool.

But after refexion, I figured out maybe my need was not so relevant : semantically, a Record is a bunch of properties defining one (and only) object. Here, we are trying to create group of X similar objects, with an index to access each of them quickly. It looks like we need a Map, not a Record. :D

So I suggest you to modify your code to use a Map instead of a Record. In addition to solving this init problem, I am sure it will better match your global needs all around your application.

export enum Key {
  A = 'a',
  B = 'b',
  C = 'c'
}

export interface Value {
  isAvailable: boolean;
  reasons: string[];
}

// Need to define a class to use the 'new Access()' further.
class Access extends Map<Key, Value > {}


export function access(): Access {
    const featureAccess: Access = new Access();
    Object.keys(Key).forEach((eachKey: string) => {
      featureAccess.set(eachKey as Key, {
        isAvailable: ..., // Populate from API
        reasons: ...// Populate from API
      });
    });
    return featureAccess;
}

And further, you can easily do :

const obj: Access = access();

obj.has(Key.A);
obj.get(Key.A);
obj.keys();
obj.values();
obj.foreach(...);
obj.delete();
obj.size;
.....

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map

Upvotes: 0

Johannes Brodwall
Johannes Brodwall

Reputation: 7821

You can at least make it work by going through an any type.

export function access() {
    const featureAccess: any = {};
    Object.keys(Key).forEach((eachKey: string) => {
      const feature = Access[eachKey];
      featureAccess[feature] = {
        isAvailable: ..., // Populate from API
        reasons: ...// Populate from API
      };
    });
    return featureAccess as Access;
}

This is the only way I've found to avoid the Record type to be optional (i.e. Record<Foo, Bar|undefined>). It feels like a hack to go through any so I would love an improvement.

Upvotes: 1

lieahau
lieahau

Reputation: 518

this question may have been quite a while, but in case there are people who want to know the answer, we can use Partial for the Record.

enum Key {
  A = 'a',
  B = 'b',
  C = 'c'
}

interface Value {
  isAvailable: boolean;
  reasons: string[];
}

type Access = Partial<Record<Key, Value>>;

then we can iterate the enum and assign it to the Record variable.

const featureAccess: Access = {};
Object.keys(Key).forEach((enumKey) => {
  console.log(enumKey);
  featureAccess[enumKey] = ..... // assign the value
});

or we can use the enum values as well

const featureAccess: Access = {};
Object.values(Key).forEach((enumValue) => {
  console.log(enumValue);
  featureAccess[enumValue] = ..... // assign the value
});

Upvotes: 5

Related Questions