Home > other >  Typescript remap array to object with intellisense on keys
Typescript remap array to object with intellisense on keys

Time:07-28

I am currently having some trouble understanding Typescript Mapped Types, where they list that "Key remapping" is possible, but when actually trying that with a array, it just errors with Type '"key"' cannot be used to index type 'A[E]'.

Note: this is a types question, not a runtime question.

Example Code:

interface ListEntry {
  key: string;
  prop: string;
}

type MapListEntryArrayToRecord<A extends ListEntry[]> = {
  // Error "Type '"key"' cannot be used to index type 'A[E]'"
  [E in keyof A as A[E]['key']]: A[E]['prop'];
};

const input: ListEntry[] = [
  { key: 'key1', prop: 'Prop1' },
  { key: 'key2', prop: 'Prop2' },
];

function someFunction<List extends ListEntry[]>(list: List): MapListEntryArrayToRecord<List> {
  // some implementation
  const ret: Record<string, ListEntry['prop']> = {};
  for (const { key, prop } of list) {
    ret[key] = prop;
  }

  return ret as MapListEntryArrayToRecord<List>;
}

const mapped = someFunction(input);

// expecting intellisense for all available keys
mapped;

// example of what i would expect
const expectedOutput = {
  key1: 'Prop1',
  key2: 'Prop2',
};

// expecting intellisense for all available keys
expectedOutput;

PS: i tried to search for a answer, but could not find any typescript examples on how to do this.

CodePudding user response:

There are a few problems here.

First the input object. If you use an explicit type, all information about specific values of the object you assign to it is lost. So we have to remove the type and add as const.

const input = [
  { key: 'key1', prop: 'Prop1' },
  { key: 'key2', prop: 'Prop2' },
] as const;

Using as const will convert the array to readonly. We will have to modify the generic types to accept both normal and readonly arrays.

type MapListEntryArrayToRecord<A extends readonly ListEntry[]> = {
  /* ... */
};

function someFunction<List extends readonly ListEntry[]>(list: List): MapListEntryArrayToRecord<List> {
  /* ... */
}

Now to your original question. TypeScript has problems with knowing that A[keyof A] has a key property. This boils down to the fact that generic types indexed with keys of generic types and similar expressions will often not be fully evaluated by the compiler.

So we have to remind TypeScript that this property exists by explicitly checking.

type MapListEntryArrayToRecord<A extends readonly ListEntry[]> = {
  [E in keyof A as A[E] extends { key: infer K extends string } ? K : never]: 
    A[E]['prop'];
};

Playground

  • Related