I'm trying to write the typescript signature of a generic "groupBy" function that would "spread" a discriminated type union array into a record where each field of the record is a possible discriminator value and points to an array of objects of a concrete type from the union.
Example:
interface Dog {
type: 'dog'
dogMetadata: {}
}
interface Cat {
type: 'cat'
catMetadata: {}
}
type Animal = Dog | Cat
const animals: Animal[] = [{ type: 'dog', dogMetadata: {} }, { type: 'cat', catMetadata: {} }]
Each interface has a common discriminator property and no other common properties.
Here is simple "groupBy" signature that does not spread the type union values, forcing me to downcast the values of the record:
function groupBy<T, K extends string>(arr: T[], keyExtractor: (element: T) => K): Record<K, T[]>
const animalsByType: Record<'dog' | 'cat', Animal[]> = groupBy(animals, it => it.type)
const dogs: Dog[] = animalsByType['dog'] as Dog[] // Must downcast Animal[] to Dog[]
How can I make a "groupBy" that is aware of the concrete types of the discriminated union type? I would want something like this:
const animalsByType: { dog: Dog[], cat: Cat[] } = groupBy(animals, it => it.type)
const dogs: Dog[] = animalsByType['dog'] // animalsByType.dog is known to be Dog[] by typescript
The implementation is easy, having trouble with the Typescript part :) I'm looking for a generic solution that doesn't make assumptions, like the name of the discriminator property or the amount of types in the type union.
Follow-up question
Would it be possible to make the same signature work when the union is nested inside another class?
interface Holder<T> {
data: T
}
const animalHolders: Holder<Animal>[] = animals.map(data => ({ data }))
const dogHolders: Holder<Dog> = groupBy(animalHolders, it => it.data.type) // Any way of doing this?
Thanks for the help.