Home > OS >  Infer correct typings when converting an array into an object based on a key
Infer correct typings when converting an array into an object based on a key

Time:08-20

I have an array of items, each with a unique type string property. I want to convert this into a type where the type is the key and the value is the original item.

The first thing I do is to ensure I have a correctly typed tuple

type MyObject = { type: string; [key: string]: any }

const createObjectList = <T extends ReadonlyArray<MyObject>>(items: T) => items;

const objectList = createObjectList([
  { type: 'a', value: 'foo' },
  { type: 'b', value: 10 },
] as const)

// objectList[0].value is corectly infered as 'foo'
// objectList[1].value is corectly infered as 10

After that, I try to convert it into the object type.

type ObjectDictionary = {
  [key in typeof objectList[number]['type']]: typeof objectList[number];
}

const typed: ObjectDictionary = undefined as any;

The issue here is that, of course, the value is now a union type off all possible values instead of the one specific to that key, so the result is equal to

type ObjectDictionary = {
  a: { value: string | number };
  b: { value: string | number };
}

instead of

type ObjectDictionary = {
  a: { value: string };
  b: { value: number };
}

Any idea how to get the individual item type in ObjectDictionary?

node: this isn't the actual code that I am working with; I just tried to simply the core issue

CodePudding user response:

The reason you are getting a union is of course because you are using typeof objectList[number] as the type of the property, here is no relation to type.

If you are using an older version of TS you can use Extract to get only the item type that as the same type as the current key. Playground Link

You can also use the as clause in mapped types (since TS 4.1, PR) to map over the union of item type in the array. As the key you can select the type in the as clause and then you have the corresponding item type in the mapping type parameter:

type MyObject = { type: string; [key: string]: any }

const createObjectList = <T extends ReadonlyArray<MyObject>>(items: T) => items;

const objectList = createObjectList([
  { type: 'a', value: 'foo' },
  { type: 'b', value: 10 },
] as const)

type ObjectDictionary = {
  [TItem in typeof objectList[number] as item['type']]: Omit<TItem, 'type>'>;
}

const typed: ObjectDictionary = undefined as any;

Playground Link

  • Related