1

I am writing this function that gets an array and it turns it into a collection where items are appened by an item key.

The code is the following:

export const arrayToCollection = <T, K>(
  list: T[],
  key: K
): CollectionById<T> => {
  const collection: CollectionById<T> = {};

  list.forEach((item: T) => {
    collection[item[key]] = item;
  });

  return collection;
};

However, I am getting the following typescript error: enter image description here

Here is the code in typescriptlang http://www.typescriptlang.org/play/#src=export%20interface%20CollectionById%3CT%3E%20%7B%0D%0A%20%20%5Bkey%3A%20string%5D%3A%20T%3B%0D%0A%7D%0D%0A%0D%0Aexport%20const%20arrayToCollection%20%3D%20%3CT%2C%20K%3E(%0D%0A%20%20list%3A%20T%5B%5D%2C%0D%0A%20%20key%3A%20K%0D%0A)%3A%20CollectionById%3CT%3E%20%3D%3E%20%7B%0D%0A%20%20const%20collection%3A%20CollectionById%3CT%3E%20%3D%20%7B%7D%3B%0D%0A%0D%0A%20%20list.forEach((item%3A%20T)%20%3D%3E%20%7B%0D%0A%20%20%20%20collection%5Bitem%5Bkey%5D%5D%20%3D%20item%3B%0D%0A%20%20%7D)%3B%0D%0A%0D%0A%20%20return%20collection%3B%0D%0A%7D%3B

vanegeek
  • 713
  • 2
  • 10
  • 22
  • What is the definition of `CollectionById` ? – Titian Cernicova-Dragomir Jul 09 '18 at 15:06
  • export interface CollectionById { [key: string]: T; } – vanegeek Jul 09 '18 at 15:07
  • 1
    See 2nd and 3rd error message: Your type for `key`is `K` but should be `string` and not something generic according to your definition for `CollectionById` – Mörre Jul 09 '18 at 15:12
  • thanks! after setting the `key` to `string` I am getting a different error in `item[key]` `[ts] Element implicitly has an 'any' type because type '{}' has no index signature. (parameter) key: string` – vanegeek Jul 09 '18 at 15:16
  • 1
    Maybe https://stackoverflow.com/questions/32968332/how-do-i-prevent-the-error-index-signature-of-object-type-implicitly-has-an-an – Mörre Jul 09 '18 at 15:20
  • [TypeScript Index Signature](https://basarat.gitbooks.io/typescript/docs/types/index-signatures.html) does not allow any type for index. Only `string` and `number` are allowed. – Hyuck Kang Jul 09 '18 at 15:22

1 Answers1

0

You need to restrict K to a string and also you need to ensure that when indexing the accessed field is also a string, we can use a mapped type to ensure this:

interface CollectionById<T> {
    [n: string]: T
}

export const arrayToCollection = <T extends { [P in K]: string }, K extends string>(
    list: T[],
    key: K
): CollectionById<T> => {
    const collection: CollectionById<T> = {};

    list.forEach((item: T) => {
        collection[item[key]] = item;
    });

    return collection;
};

let list = [{ id: "A", age: 10 }, { id: "B", age: 10 }]

let grouped = arrayToCollection(list, 'id'); //ok
let grouped = arrayToCollection(list, 'age'); //error

Note We can also allow indexing by number if we change the generic constraint to be T extends { [P in K]: string | number }. Then we could also write:

let list = [{ id: 1, age: 10, data: {} }, { id: 2, age: 10, data: {} }]

let grouped = arrayToCollection(list, 'id');
let grouped3 = arrayToCollection(list, 'data'); // error neeed to be string or number
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • What's wrong with just restricting `K extends keyof T` (and not explicitly restricting T at all), apart from different behaviour in TS 2.8 and 2.9? – Cerberus Jul 10 '18 at 03:08
  • 1
    @Cerberus nothing wrong with it generally, but in this case we need another restriction. Since we use `collection[item[key]]` we need `item[key]` to be a value which can be used as an index, so `T[K]` must be a string (or a number). We can achieve such a restriction in 2 ways, we can filter the keys, which works well for callers but does nothing to type `T[K]` inside the function, the other way is to use a mapped type as I did – Titian Cernicova-Dragomir Jul 10 '18 at 03:12