Logo

자바스크립트의 groupBy() API 사용법

데이터를 특정 기준에 따라 분류하는 것은 자바스크립트로 데이터 처리를 할 때 자주 필요한 작업인데요. 그런데 아직도 데이터 그룹화를 위해서 reduce() 함수나 Lodash 라이브러리를 사용하시나요?

이번 포스팅에서는 자바스크립트에서 데이터를 그룹화를 위해 쓸 수 있는 비교적 새로운 API인 groupBy()에 대해서 알아보도록 하겠습니다.

기존 데이터 그룹화 방법

우선 예전에 자바스크립트에서 데이터 그룹화가 얼마나 불편했는지 살펴볼까요?

다음과 같이 여러 사용자의 이름, 나이, 국가를 담은 배열이 주어졌을 때,

const users = [
  { name: "John", age: 25, country: "US" },
  { name: "Jane", age: 30, country: "KR" },
  { name: "Robin", age: 22, country: "CA" },
  { name: "Doe", age: 13, country: "US" },
  { name: "Smith", age: 20, country: "KR" },
];

아래와 같이 국가를 기준으로 사용자를 분류하고 싶다고 가정해봅시다.

{
  US: [
    { name: "John", age: 25, country: "US" },
    { name: "Doe", age: 13, country: "US" }
  ],
  KR: [
    { name: "Jane", age: 30, country: "KR" },
    { name: "Smith", age: 20, country: "KR" }
  ],
  CA: [
    { name: "Robin", age: 22, country: "CA" }
  ],
}

지금까지도 가장 많이 쓰이던 방법은 배열의 reduce() 함수를 사용하는 것입니다.

const usersByCountry = users.reduce((users, user) => {
  const country = user.country;
  if (!(country in users)) {
    users[country] = [];
  }
  users[country].push(user);
  return users;
}, {});

console.log(usersByCountry);

하지만 이 코드는 딱 봐도 그닥 직관적이지가 않습니다. 코드의 가독성이 떨어진다는 큰 단점이 있습니다.

자바스크립트 배열의 reduce() 함수를 사용하는 방법에 대해서는 별도 포스팅에서 자세히 다루고 있으니 참고하세요.

이러한 단점을 극복하기 위해서 Lodash라는 라이브러리가 많이 사용했는데요.

우선 npm이나 Bun을 사용해서 패키지를 설치해야합니다.

$ npm add lodash
$ bun add lodash

그 다음 Lodash 라이브러리의 groupBy()라는 함수를 호출하는데, 첫 번째 인자로 분류할 배열을 넘기고, 두 번째 인자로 분류 기준이 될 속성명을 넘기면 됩니다.

import _ from "lodash";

const usersByCountry = _.groupBy(users, "country");

console.log(usersByCountry);

하지만 라이브러리를 쓰면 그 나름대로 유지보수 비용이 증가하고, 외부 패키지를 사용할 수 없는 환경도 있어서 근본적인 해결책은 되지 않았습니다.

groupBy() 기본 사용

외부 라이브러리 없이도 간단한 문법으로 데이터를 그룹화할 수 있도록 자바스크립트의 ObjectMap 클래스에는 groupBy()라는 정적 메서드가 추가되었습니다.

Object.groupBy()는 첫 번째 인자로 배열, 두 번째 인자로 분류 기준이 될 콜백 함수를 받습니다.

위에서 사용한 동일한 배열로 같이 실습을 해볼께요.

const users = [
  { name: "John", age: 25, country: "US" },
  { name: "Jane", age: 30, country: "KR" },
  { name: "Robin", age: 22, country: "CA" },
  { name: "Doe", age: 13, country: "US" },
  { name: "Smith", age: 20, country: "KR" },
];

국가 속성을 기준으로 사용자를 분류해야하니, 콜백 함수에서 각 사용자의 country 속성을 읽어서 그대로 반환합니다.

const usersByCountry = Object.groupBy(users, ({ country }) => country);

console.log(usersByCountry);

그러면 다음과 같이 각 국가가 키로 해당 국적자 목록이 값으로 객체가 만들어 질 것입니다.

{
  US: [
    { name: "John", age: 25, country: "US" },
    { name: "Doe", age: 13, country: "US" }
  ],
  KR: [
    { name: "Jane", age: 30, country: "KR" },
    { name: "Smith", age: 20, country: "KR" }
  ],
  CA: [
    { name: "Robin", age: 22, country: "CA" }
  ],
}

Map.groupBy()Object.groupBy()와 사용법의 거의 비슷한데 분류 결과를 객체가 아닌 맵(Map)에 담아준다는 점에서 차이가 있습니다. 맵은 객체와 달리 키로 문자열뿐 아니라 다른 자료형도 사용할 수 있어 좀 더 유연하게 그룹화를 처리할 수 있겠죠?

const usersByCountry = Map.groupBy(users, ({ country }) => country);

console.log(usersByCountry);

Map.groupBy()를 통해 그룹화를 해보면 각 국가를 키로 해당 국적자 목록을 값으로 맵이 얻어집니다.

new Map([
  [
    "US",
    [
      {
        name: "John",
        age: 25,
        country: "US",
      },
      {
        name: "Doe",
        age: 13,
        country: "US",
      },
    ],
  ],
  [
    "KR",
    [
      {
        name: "Jane",
        age: 30,
        country: "KR",
      },
      {
        name: "Smith",
        age: 20,
        country: "KR",
      },
    ],
  ],
  [
    "CA",
    [
      {
        name: "Robin",
        age: 22,
        country: "CA",
      },
    ],
  ],
]);

groupBy() 고급 사용

groupBy() API는 두 번째 인자로 콜백 함수를 받기 때문에 꼭 특정 속성의 값 뿐만 아니라 정말 다양한 기준으로 분류가 가능합니다.

예를 들어서, 25살을 기준으로 젊은이와 늙은이를 분류해보겠습니다.

const usersByAge = Object.groupBy(users, ({ age }) =>
  age < 25 ? "YOUNG" : "OLD"
);

console.log(usersByAge);
{
  "OLD": [
    {
      "name": "John",
      "age": 25,
      "country": "US"
    },
    {
      "name": "Jane",
      "age": 30,
      "country": "KR"
    }
  ],
  "YOUNG": [
    {
      "name": "Robin",
      "age": 22,
      "country": "CA"
    },
    {
      "name": "Doe",
      "age": 13,
      "country": "US"
    },
    {
      "name": "Smith",
      "age": 20,
      "country": "KR"
    }
  ]
}

이번에는 이름이 알파벳 J로 시작하는 사람과 아닌 사람을 분류해보겠습니다.

const usersByName = Object.groupBy(users, ({ name }) => name.startsWith("J"));

console.log(usersByName);
{
  "true": [
    {
      "name": "John",
      "age": 25,
      "country": "US"
    },
    {
      "name": "Jane",
      "age": 30,
      "country": "KR"
    }
  ],
  "false": [
    {
      "name": "Robin",
      "age": 22,
      "country": "CA"
    },
    {
      "name": "Doe",
      "age": 13,
      "country": "US"
    },
    {
      "name": "Smith",
      "age": 20,
      "country": "KR"
    }
  ]
}

브라우저 지원

groupBy() API는 공식적으로 지원이 끝난 인터넷 익스플로러(Internet Explorer)를 제외하면 모든 모던 브라우저에서 안전하게 사용할 수 있습니다.

마치면서

지금까지 간단한 실습을 통해서 Object.groupBy()Map.groupBy() 메서드를 어떻게 사용하는지 알아보았습니다. 이 두 개의 정적 메서드를 잘 활용하셔서 라이브러리 없이 간결한 코드로 데이터를 효과적으로 분류하실 수 있으셨으면 좋겠습니다.