Home > front end >  Typescript converting an array of objects to a Map
Typescript converting an array of objects to a Map

Time:11-05

I have the following array:

const myArray = [{
   a: 'test',
   b: 1
}, {
   a: 'test1',
   b: 2
}];

I would like to convert this array to a Map, based on one of it's properties, and maintain a strongly typed code, with a function like this:

convertArrayToMap<TObject extends {}, TArray extends TObject[], TProperty extends keyof TObject>(inputArray: TArray, property: TProperty): Map<TProperty, TObject> {
   // ... logic goes here <-- I AM AWARE of the logical code, this is not what I need, I need help in typing my function
}

I am having two troubles with the typings here:

  1. when I try to use the function, typescript implies TProperty as never, yet it correctly identifies the TArray type. Why is this happening?

  2. the way how this is written at the moment, the returned Map is set to have keys of the TProperty value, but instead of that, I would need keys typed as the type of the TProperty from the object. I tried with typeof TProperty but typescript complains.

In general, here is an example, on how the function should work:

const map = convertArrayToMap(myArray, 'a'); // this should be a Map<string, { a: string, b: number}> type 
console.log(map.get('test').b); // this logs 1 on the screen

CodePudding user response:

Try this one:

function convertArrayToMap<
  TObject extends object,
  TProperty extends keyof TObject,
>(
  inputArray: TObject[],
  property: TProperty,
): Map<TObject[TProperty], TObject> {
  // ...
}

Here's also a link to playground.

CodePudding user response:

If you are interested in infering the literal type, you can consider thsis example:

export { }; // ignore this

type Overloading<
  Tuple extends any[],
  Prop extends keyof Tuple[number],
  Result extends Map<never, never> = Map<never, never>
  > =
  Tuple extends []
  ? Result
  : Tuple extends [infer Head, ...infer Tail]
  ? Prop extends keyof Head ? Overloading<Tail, Prop, Result & Map<Head[Prop], Head>> : never : never

declare function convertArrayToMap<
  Key extends PropertyKey,
  Value extends string | number,
  Obj extends Record<Key, Value>,
  Prop extends keyof Obj,
  Tuple extends Obj[]
>(
  tuple: [...Tuple],
  property: Prop,
): Overloading<[...Tuple],Prop>;


const map = convertArrayToMap([
  {
    id: "123",
    name: "John Doe",
    age: 42,
  },
  {
    id: "456",
    name: "Jane Doe",
    age: 17,
  },
], 'id');

// {
//     id: "123";
//     name: "John Doe";
//     age: 42;
// }
const person = map.get("123"); // this is `{id: 123, name:'JohnDoe'} | undefined`
const name = person!.name; "John Doe"

PLayground

Overloading iterates through provided tuple, and creates an overloading of Map data structure for appropriate key.

Drawback: it works only with literal argument or with as const assertion.

More about inference on function arguments you can find in my blog

Keep in mind, that intersection of Map<string, number> & Map<number,string> creates an overloading:


type Overloading = Map<string, number> & Map<number,string>;

declare const map: Overloading;

map.get('hello') // number | undefined
map.get(42) // string | undefined
  • Related