6

I am trying to typed the below utility function which takes in an array and return an object as below

const convertListToMap = (list, key, secondKey) => {
  const map = {};
  
  for (const ele of list) {
    map[ele[key]] = ele[secondKey]
  }
  
  return map;
}


console.log(convertListToMap([{name: 'isaac', age: 17}, {name: 'john', age: 20}], 'name', 'age'));

console.log(convertListToMap([{race: 'chinese', language: 'mandarin'}, {race: 'malay', language: 'melayu'}], 'race', 'language'));

As you can tell key and secondKey is sort of dynamic, depending on the parameter list, so I guess generics is the way to go. Below is my attempt

const convertListToMap = <
  T extends object,
  U extends keyof T,
  V extends keyof T
>(
  list: T[],
  key: U,
  secondKey: V
) => {
  const map = {} as Record<U, V>;
  for (const ele of list) {
    map[ele[key]] = ele[secondKey];
  }
  return map;
};

However, the above giving me error Type 'T[U]' cannot be used to index type 'Record<U, V>', wondering which part I've done wrong?

UPDATES

My latest attempt is to update the signature of map as below

  const map = {} as Record<T[U], T[V]>;
  for (const ele of list) {
    map[ele[key]] = ele[secondKey];
  }
  return map;

With this latest attempt, value assignment to map variable is fine, but I'm getting error at Record declaration line with below error

Type 'T[U]' does not satisfy the constraint 'string | number | symbol'. Type 'T[keyof T]' is not assignable to type 'string | number | symbol'. Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'string | number | symbol'. Type 'T[string]' is not assignable to type 'string | number | symbol'. Type 'T[string]' is not assignable to type 'symbol'. Type 'T[keyof T]' is not assignable to type 'symbol'. Type 'T[U]' is not assignable to type 'symbol'. Type 'T[keyof T]' is not assignable to type 'symbol'. Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'symbol'. Type 'T[string]' is not assignable to type 'symbol'.ts(2344)

3
  • Ignore me (deleted the comment). I think the main problem is that your map is not type Record<U, V> since you're putting in the values from T, not the keys Commented Sep 28, 2021 at 7:49
  • @Phil: Good point, I even tried const map = {} as Record<T[U], T[V]>; but it doesn't work either haha Commented Sep 28, 2021 at 7:50
  • @Phil: I've finally found a solution, maybe can take a look if you're interested :D Commented Sep 28, 2021 at 8:22

1 Answer 1

4

The problem with the last attempt was that Record seemed to expect only string | number | symbol, and since TypeScript unsure what T[U] gonna be, hence it threw the error. I found that we can tell TS a little bit more about T as below

const convertListToMap = <
  T extends { [key: string|number]: string | number },
  U extends keyof T,
  V extends keyof T
>(
  list: T[],
  key: U,
  secondKey: V
) => {
  const map = {} as Record<T[U], T[V]>;
  for (const ele of list) {
    map[ele[key]] = ele[secondKey];
  }
  return map;
};

From the above, we are telling typescript that T is gonna be an object with key of string, and the value is gonna be of type string | number, with that, TypeScript is happy with the definition Record<T[U], T[V]>

This solution works great and TS can infer correctly

const personObj = convertListToMap([{name: 'isaac', age: 17}, {name: 'john', age: 20}], 'name', 'age')
// const personObj: Record<string, number>

const languageObj = convertListToMap([{race: 'chinese', language: 'mandarin'}, {race: 'malay', language: 'melayu'}], 'race', 'language')
// const languageObj: Record<string,string>
Sign up to request clarification or add additional context in comments.

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.