실무에서 데이터를 다루다 보면 객체 배열 안에서 특정 키 조합이 동일한 항목들을 하나로 묶고, 묶인 항목들의 수치 합계와 평균을 구해야 하는 경우가 매우 흔합니다. 이 글에서는 JavaScript의 reduce() 메서드를 활용해 여러 필터 조건(공급자명 + 카테고리)을 기준으로 JSON 형태의 배열을 그룹화하고, 포인트 합계와 평균을 한 번에 계산하는 방법을 단계별로 살펴봅니다.
문제 정의
다음과 같이 공급자 이름(SupplierName), 카테고리(Category), 점수(Points)를 담은 객체 배열이 있다고 가정해 보겠습니다. 일부 항목에는 점수가 undefined로 들어 있어, 이런 값도 적절히 처리해야 한다는 점에 유의해야 합니다.
const arr = [
{ SupplierName: 'John', Category: 'A', Points: 3 },
{ SupplierName: 'John', Category: 'A', Points: 11 },
{ SupplierName: 'John', Category: 'A', Points: undefined },
{ SupplierName: 'John', Category: 'B', Points: 2 },
{ SupplierName: 'John', Category: 'B', Points: 6 },
{ SupplierName: 'Praveen', Category: 'A', Points: 3 },
{ SupplierName: 'Praveen', Category: 'A', Points: 7 }
];
이 배열을 입력받아 처리할 함수는 아래 세 가지 작업을 수행해야 합니다.
- 그룹화 — SupplierName과 Category가 모두 같은 객체들을 하나의 고유 객체로 병합합니다.
- 합계 계산 — 같은 그룹에 속한 객체들의 Points 값을 모두 더합니다.
- 평균 추가 — 각 그룹 객체에 해당 공급자의 평균 점수를 담는 Average 필드를 새로 추가합니다.
따라서 최종 출력 결과는 다음과 같은 형태가 되어야 합니다.
const output = [
{ SupplierName: 'John', Category: 'A', Points: 14, Average: 7 },
{ SupplierName: 'John', Category: 'B', Points: 8, Average: 4 },
{ SupplierName: 'Praveen', Category: 'A', Points: 10, Average: 5 }
];
해결 코드
핵심 아이디어는 reduce()로 배열을 순회하면서 '공급자명 + 카테고리' 조합을 키로 삼아 누적 객체(acc)에 그룹을 만들고, 이후 Object.keys()로 각 그룹의 평균을 일괄 계산하는 것입니다.
const groupAndAverage = (arr = []) => {
// 1단계: SupplierName + Category 조합을 기준으로 그룹화
const groups = arr.reduce((acc, obj) => {
const key = obj.SupplierName + '_' + obj.Category;
if (acc[key]) {
// 이미 존재하는 그룹이면 점수를 누적
if (obj.Points) {
acc[key].Points += obj.Points;
++acc[key].Average; // 이 시점에는 Average가 '항목 개수' 역할을 함
}
} else {
// 새 그룹 생성 (첫 단계에서는 Average를 개수 카운터로 사용)
acc[key] = { ...obj };
acc[key].Average = 1;
}
return acc;
}, {});
// 2단계: 누적된 합계를 개수로 나누어 실제 평균값으로 변환
return Object.keys(groups).map((key) => {
groups[key].Average = Math.round(groups[key].Points / groups[key].Average);
return groups[key];
});
};
console.log(JSON.stringify(groupAndAverage(arr), null, 4));
동작 원리
코드가 어떤 순서로 동작하는지 단계별로 정리하면 다음과 같습니다.
- 1단계 — 그룹 키 생성:
obj.SupplierName + '_' + obj.Category처럼 두 필드를 결합한 문자열을 고유 키로 사용합니다. 이렇게 하면 'John + A', 'John + B', 'Praveen + A'처럼 조건이 완전히 같은 항목들만 같은 그룹으로 묶입니다. - 2단계 — 값 누적: 이미 존재하는 그룹이라면 Points를 더하고, 임시 카운터 역할을 하는 Average를 1씩 증가시킵니다.
if (obj.Points)조건 덕분에undefined,null같은 유효하지 않은 점수는 자동으로 제외됩니다. - 3단계 — 평균 변환: 모든 순회가 끝나면 각 그룹의 Points(합계)를 Average(개수)로 나누고,
Math.round()로 반올림해 최종 평균을 구합니다.
예를 들어 'John + A' 그룹의 경우 점수 3과 11만 유효하므로 합계는 14, 개수는 2가 되고 평균은 14 ÷ 2 = 7이 됩니다. undefined인 항목은 합계와 개수 계산 모두에서 자연스럽게 건너뛰어집니다.
실행 결과
위 코드를 실행하면 콘솔에 다음과 같이 그룹별 합계와 평균이 정리된 배열이 출력됩니다.
[
{
"SupplierName": "John",
"Category": "A",
"Points": 14,
"Average": 7
},
{
"SupplierName": "John",
"Category": "B",
"Points": 8,
"Average": 4
},
{
"SupplierName": "Praveen",
"Category": "A",
"Points": 10,
"Average": 5
}
]
마무리
이 패턴은 reduce() 하나만으로 그룹화·집계·평균 계산을 모두 처리할 수 있는 실용적인 접근 방식입니다. 키를 생성하는 부분에서 필드를 더 추가하거나(예: 날짜 + 지역), 반올림 대신 소수점 자릿수를 지정하는 toFixed()를 사용하는 식으로 요구 사항에 맞게 쉽게 확장할 수 있습니다. 대량의 JSON 데이터를 집계해야 할 때 유용하게 활용해 보시기 바랍니다.