DEV Community

loading...
Cover image for Split collection based on enum value with typescript

Split collection based on enum value with typescript

Volodymyr Yepishev
I like coding :)
・2 min read

Enumerations are an awesome feature of typescript, you can create a set of distinct cases with numeric or string-based enums.

For example, if you're working on a game of cards and figure out you could actully use enum to define the suits:

enum Suit {
  Clubs = '♣️',
  Spades = '♠️',
  Diamonds = '♦️',
  Hearts = '♥️',
}
Enter fullscreen mode Exit fullscreen mode

Now, if you have a collection of objects and each object in the collection has a value represented by an enum, you can actually write a pretty neat function to split it into smaller collections, each limited to a certain enum value. In our case we could split a deck of cards into clubs, spades, diamods and hearts correspondently.

For the sake of simplicity our deck will have only 4 cards, namely 4 aces, one from each suit, here's our small deck:

interface Card {
  name: string;
  suit: Suit;
}

const deck: Card[] = [
  {
    name: 'Ace',
    suit: Suit.Clubs
  },
  {
    name: 'Ace',
    suit: Suit.Diamonds
  },
  {
    name: 'Ace',
    suit: Suit.Hearts
  },
  {
    name: 'Ace',
    suit: Suit.Spades
  }
];
Enter fullscreen mode Exit fullscreen mode

With typescript we will be looking forward to obtaining somehow strongly typed object that would be of the following interface:

{
  '♣️': Card[],
  '♠️': Card[],
  '♦️': Card[],
  '♥️': Card[],
}
Enter fullscreen mode Exit fullscreen mode

Basically we will have enum values as keys and each will hold corresponding array with cards of its type.

So, let's invent a function for producing such object. But before that we would need a generic type that can take enum and a value type and produce a type for the object, which would have its properties based on enum values. Let's make it as abstract as possible:

type GroupedValues<T extends PropertyKey, V> = {
  [K in T]: V
}
Enter fullscreen mode Exit fullscreen mode

Now we're ready to create our grouping function with the following signature:

function groupItemsBy<T, P extends PropertyKey>(items: T[], groupingKey: keyof T, enu: Record<string, string>): GroupedValues<P, T[]>
Enter fullscreen mode Exit fullscreen mode

It takes two generics, the first being the type of the items in the collection we are about to, and the second is the enum. As arguments we will pass the collection, the name of the field, by which we are going to do the grouping, and the enum itself, which is basically a Record<string, string> type object.

Now what's left is to grab enum values, use them to make an empty object with arrays to store items from our collection, and then to iterate our collection pushing each item to the corresponging array which has the same name as the passed property value:

function groupItemsBy<T, P extends PropertyKey>(items: T[], groupingKey: keyof T, enu: Record<string, string>): GroupedValues<P, T[]> {
  const result: GroupedValues<P, T[]> = Object.values(enu).reduce((a: GroupedValues<P, T[]>, b) => {
    a[b as P] = [];
    return a;
  }, {} as GroupedValues<P, T[]>);

  items.forEach(i => {
    const key = i[groupingKey] as unknown as P;
    result[key].push(i);
  });

  return result;
}
Enter fullscreen mode Exit fullscreen mode

Now we can easily split our deck by suit:

const { "♣️": clubs, "♦️": diamonds, "♥️": hearts, "♠️": spades } = groupItemsByProperty<Card, Suit>(deck, 'suit', Suit);
Enter fullscreen mode Exit fullscreen mode

Doesn't look difficult at all, does it? What's cool is the we've got intellisense all the way :)

Oh, you can also check it out in the typescript playground.

Discussion (0)