TypeScript Version: 3.9.2
Search Terms:
Record
Symbol
Code
const groupArrayByObjectProperty = <T extends any, K extends keyof T>(
array: T[], attributeGetter: (item: T) => number | string,
): Record<T[K], T[]> => array.reduce(
(previous, current) => {
const value = attributeGetter(current);
previous[value] = (previous[value] || []).concat(current);
return previous;
},
{} as Record<T[K], T[]>,
);
console.log(groupArrayByObjectProperty([{a: 'a', b: 'b'}], (x) => x.a));
The function's purpose is to create an object from an array of objects, grouped by a value of the objects.
[
{a: 1, b: '2', c: 'e'},
{a: 2, b: '0', c: 'x'},
{a: 2, b: '2', c: 'p'},
{a: 1, b: '2', c: 'e'},
{a: 1, b: '0', c: 'c'},
{a: 3, b: '0', c: 't'},
]
When grouped by the value of a becomes:
{
1: [
{a: 1, b: '2', c: 'e'},
{a: 1, b: '2', c: 'e'},
{a: 1, b: '0', c: 'c'},
],
2: [
{a: 2, b: '0', c: 'x'},
{a: 2, b: '2', c: 'p'},
],
3: [
{a: 3, b: '0', c: 't'},
],
},
The important aspect of this function is that the typing of the output object is correct.
Expected behavior:
The code compiles and works like it did on TS 3.8
Actual behavior:
I'm now getting these errors:
src/functions/group-array-by-object-property.ts:8:11 - error TS2344: Type 'T[K]' 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[K]' 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'.
8 ): Record<T[K], T[]> => array.reduce(
~~~~
src/functions/group-array-by-object-property.ts:11:13 - error TS7053: Element implicitly has an 'any' type because expression of type 'string | number' can't be used to index type 'Record<T[K], T[]>'.
No index signature with a parameter of type 'string' was found on type 'Record<T[K], T[]>'.
11 previous[value] = (previous[value] || []).concat(current);
~~~~~~~~~~~~~~~
src/functions/group-array-by-object-property.ts:11:32 - error TS7053: Element implicitly has an 'any' type because expression of type 'string | number' can't be used to index type 'Record<T[K], T[]>'.
No index signature with a parameter of type 'string' was found on type 'Record<T[K], T[]>'.
11 previous[value] = (previous[value] || []).concat(current);
~~~~~~~~~~~~~~~
src/functions/group-array-by-object-property.ts:14:18 - error TS2344: Type 'T[K]' does not satisfy the constraint 'string | number | symbol'.
Type 'T[K]' is not assignable to type 'symbol'.
14 {} as Record<T[K], T[]>,
~~~~
Did something change in the TS compiler that broke it? Or should it never have worked from the start? If so, what would have been the correct typing?
Playground Link:
https://www.typescriptlang.org/play/#code/MYewdgzgLgBA5gJxAVwA4EEEIIYE8BCuA8gEYBWApsFAApKoUJS4wC8MAPACowUAeUCmAAmEGNjC4ANDADSvAUNEwA1hVwgAZjC4A+ABQAoGCfFY8ALh0BtALozsUKAgCWJZIIDiFJ4yv6XQQBbKy4ASjZdGDBkIJJGGAAfGGhXMDgpQzCrACUqEARhbmtZextbKNYo7HNcADoECmFkYAojUw6YfVRGgDcXFAgZYGQsISgIqpgAb2NO+dBIWF7sABtkCjZxJ1d3Lx9BBH0RsbAJgG45+Y6ein7B6xX1ilst7r6B5AhHtY3XxOSdjCdUWwEcx1GjTOYUu106jSgozAMFu9y+sOuAF9Mh1ppjxGI8qBCsVSjIuHZdDjTDDDIZFhAQKsKHVViA4PpECgMLVCKRKNQ6CAGExcPprNNsFYAOTYaUyEgykjSzFlfR8SZRPh1bBhGFAA
Related Issues:
I think this can be demonstrated as short as below.
let record: Record<number, string> = {}
let record2: Record<'abc'|'def', number> = {} // error here
You broke it!
I think this can be demonstrated as short as below.
let record: Record<number, string> = {} let record2: Record<'abc'|'def', number> = {} // error here
But that error is correct! {} is missing the two properties abc and def.
The first line is most likely an exception because you can never provide a value for all possible keys of Record<number, string>
The change was introduced here #29571.
This might work:
const groupArrayByObjectProperty = <T extends Record<string, string>, K extends keyof T>(
array: T[], attributeGetter: (item: T) => T[K],
): Record<T[K], T[]> => array.reduce(
(previous, current) => {
const value = attributeGetter(current);
previous[value] = (previous[value] || []).concat(current);
return previous;
},
{} as Record<T[K], T[]>,
);
I think I live with casting to as Record<'abc'|'def'>. Maybe we should add control flow for detecting undefined value for some keys.
Thank you very much, @jack-williams. That change seems to do the trick.
Most helpful comment
The change was introduced here #29571.
This might work: