2

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

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

5 Comments

yes, this solves part of my problem, but, my Map now states that it is a Map<'a', { a: string, b: number}> instead of it being a Map<string, { a: string, b: number}>, so that still remains. :( (based on the example in the original question)
Isn't Map<'a', ...> better for your needs than Map<string, ...>?
I can change this, I'm just asking
Oh, right, I misunderstood the question. You want a value of the property to be the key. Got it, on it
doesn't Map<'a', ...> mean that it is a map that has a single a key?
0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.