Home > Mobile >  Dynamically exclude a key from type
Dynamically exclude a key from type

Time:10-02

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">[]>

Playground link


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).

  • Related