To understand my question, first let me explain what I'm trying to achieve.
I wrote the following groupBy
function:
const groupBy = <T extends Record<keyof T, unknown>>(arr: T[], key: keyof T) => {
return arr.reduce((prev, { [key]: k, ...rest }) => {
if (!prev[k]) prev[k] = [] as Omit<T, keyof T>[];
prev[k].push(rest);
return prev;
}, {} as Record<keyof T, Omit<T, keyof T>[]>);
};
Now, let's say I have the following interface:
type MyType = {
city: number,
name: string
}
and the following list:
const L: MyType[] = [
{
city: 1,
name: "name1"
},
{
city: 1,
name: "name2"
},
{
city: 2,
name: "name3"
},
{
city: 2,
name: "name4"
}
]
When I call the function like this:
const grouped = groupBy(L, "city")
I want the the type of grouped
to be as follows:
Record<number, Omit<MyType, "city">[]>
I.e., the passed key to the groupBy
function should be omitted from the array items' type.
How can I achieve this? is it achievable at all?
CodePudding user response:
Yes, that's possible. Since we need to ensure that the type of the key ("city"
) is both a key of the object and a PropertyKey
(so we can use it as the key of a Record
), I think we have to use a conditional type like this:
type GroupByResult<ObjectType, KeyType extends keyof ObjectType> =
ObjectType[KeyType] extends PropertyKey
? Record<ObjectType[KeyType], Omit<ObjectType, KeyType>[]>
: never;
Then the types on the function are:
export const groupBy = <ObjectType, KeyType extends keyof ObjectType>(
arr: ObjectType[],
key: KeyType
): GroupByResult<ObjectType, KeyType> => {
// ...
};
// ...
const grouped = groupBy(L, "city")
// ^? −− const grouped: Record<number, Omit<MyType, "city">[]>
Note: There are type errors in the reduce
call. I haven't tried to address those as I think your question is about the return type. Let me know if you also need help with those (I suspect you'll always need some type assertion(s) in the implementaton, but I could be wrong).
But I did notice an error in the implementation: You're using key
where you should be using k
when indexing into prev
, the body of the reduce
callback should be:
if (!prev[k]) prev[k] = [];
prev[k].push(rest);
return prev;
Playground link bypassing the internal type errors using any
(which is often good enough for small, self-contained, easily tested functions like groupBy
).