데이터를 다루다 보면 여러 테이블의 정보가 하나의 배열에 섞여 있는 경우가 많습니다. 이럴 때 특정 키를 기준으로 데이터를 묶어주면 훨씬 다루기 쉬워집니다.
예를 들어 다음과 같은 JavaScript 배열이 있다고 가정해 보겠습니다.
const data = [
{
"dataId": "1",
"tableName": "table1",
"column": "firstHeader",
"rows": ["a", "b", "c"]
},
{
"dataId": "2",
"tableName": "table1",
"column": "secondHeader",
"rows": ["d", "e", "f"]
},
{
"dataId": "3",
"tableName": "table2",
"column": "aNewFirstHeader",
"rows": [1, 2, 3]
}
];요구 사항
이 배열을 입력으로 받아, tableName 값이 같은 객체들을 하나로 묶은 새로운 배열을 반환하는 함수를 작성해야 합니다. 즉, 고유한 tableName마다 별도의 객체를 만들고, 그 안에 해당 테이블의 column 목록과 rows 데이터를 모아 담는 것입니다.
최종적으로 기대하는 출력 결과는 다음과 같습니다.
const output = [
{
"tableName": "table1",
"column": ["firstHeader", "secondHeader"],
"rows": [["a", "b", "c"], ["d", "e", "f"]]
},
{
"tableName": "table2",
"column": ["aNewFirstHeader"],
"rows": [[1, 2, 3]]
}
];해결 방법: reduce와 Map 활용하기
배열의 reduce() 메서드와 Map 객체를 조합하면 한 번의 순회로 깔끔하게 그룹화할 수 있습니다.
const arr = [
{
"dataId": "1",
"tableName": "table1",
"column": "firstHeader",
"rows": ["val", "b", "c"]
},
{
"dataId": "2",
"tableName": "table1",
"column": "secondHeader",
"rows": ["d", "e", "f"]
},
{
"dataId": "3",
"tableName": "table2",
"column": "aNewFirstHeader",
"rows": [1, 2, 3]
}
];
const groupArray = (arr = []) => {
const res = arr.reduce((obj => (acc, val) => {
let item = obj.get(val.tableName);
if (!item) {
item = {
tableName: val.tableName,
column: [],
rows: []
};
obj.set(val.tableName, item);
acc.push(item);
}
item.column.push(val.column);
item.rows.push(val.rows);
return acc;
})(new Map), []);
return res;
};
console.log(JSON.stringify(groupArray(arr), undefined, 4));코드 동작 원리
핵심 로직을 단계별로 살펴보면 다음과 같습니다.
1. Map으로 그룹 추적
클로저 내부에 Map 인스턴스를 생성해 tableName을 키로 사용합니다. 덕분에 각 요소를 순회하면서도 O(1)에 가까운 속도로 이미 만들어진 그룹이 있는지 확인할 수 있습니다.
2. 첫 등장 시 새 그룹 생성
현재 순회 중인 요소의 tableName이 Map에 없다면, tableName, 빈 column 배열, 빈 rows 배열을 가진 새 객체를 만들어 Map과 최종 결과 배열(acc)에 동시에 등록합니다.
3. 데이터 누적
그룹 객체가 준비되면 현재 요소의 column 값과 rows 배열을 각각 push하여 데이터를 누적합니다. 이 과정을 마지막 요소까지 반복하면 그룹화가 완료됩니다.
실행 결과
콘솔에 출력되는 결과는 다음과 같습니다.
[
{
"tableName": "table1",
"column": [
"firstHeader",
"secondHeader"
],
"rows": [
["val", "b", "c"],
["d", "e", "f"]
]
},
{
"tableName": "table2",
"column": [
"aNewFirstHeader"
],
"rows": [
[1, 2, 3]
]
}
]참고: ES2024의 groupBy 활용하기
최신 JavaScript 환경(ES2024 이상)이라면 Object.groupBy() 또는 Map.groupBy()를 사용해 더 간결하게 구현할 수도 있습니다.
const grouped = Object.groupBy(arr, ({ tableName }) => tableName);
const result = Object.entries(grouped).map(([tableName, items]) => ({
tableName,
column: items.map(item => item.column),
rows: items.map(item => item.rows)
}));다만 아직 구형 브라우저나 런타임에서는 지원되지 않을 수 있으므로, 폭넓은 호환성이 필요하다면 앞서 소개한 reduce + Map 방식이 안전한 선택입니다.