문제 상황
웹 애플리케이션을 개발하다 보면 목록 데이터에 여러 개의 필터 조건을 한 번에 적용해야 하는 경우가 자주 발생합니다. 이번 글에서는 두 개의 객체 배열을 받아, 필터 배열에 정의된 모든 키-값 쌍을 동시에 만족하는 객체만 골라내는 방법을 알아보겠습니다.
먼저 아래와 같이 두 개의 배열이 있다고 가정해 봅시다.
const data = [
{"XD_A":"XDL","XD_B_1":"38","XD_B_2":"PB"},
{"XD_A":"XDR","XD_B_1":"51","XD_B_2":"PB"},
{"XD_A":"XDL","XD_B_1":"58","XD_B_2":"PB"},
{"XD_A":"XDR","XD_B_1":"38","XD_B_2":"PB"},
{"XD_A":"XDL","XD_B_1":"76","XD_B_2":"PB"},
{"XD_A":"XDR","XD_B_1":"38","XD_B_2":"PB"}
];
const filters = [{"XD_A":"XDR"}, {"XD_B_1":"38"}];- data: 실제 데이터가 담긴 객체 배열
- filters: 적용할 조건(키-값 쌍)이 담긴 배열
작성해야 할 함수의 요구사항은 다음과 같습니다.
- 두 배열을 각각 첫 번째, 두 번째 인자로 전달받습니다.
- 첫 번째 배열(data) 중에서, 두 번째 배열(filters)에 있는 모든 키-값 쌍을 함께 충족하는 객체만 선택합니다.
- 선택된 객체들을 새로운 배열에 담아 반환합니다.
즉, XD_A가 "XDR"이면서 동시에 XD_B_1이 "38"인 객체만 통과해야 하므로, 위 데이터에서 최종 결과는 다음과 같은 두 객체가 됩니다.
const output = [
{"XD_A":"XDR","XD_B_1":"38","XD_B_2":"PB"},
{"XD_A":"XDR","XD_B_1":"38","XD_B_2":"PB"}
];
throw/catch를 활용한 구현
조건이 하나라도 맞지 않으면 즉시 검사를 중단해야 하므로, 중첩된 반복문 안에서 throw로 흐름을 끊고 catch에서 처리하는 방식으로 구현할 수 있습니다.
const data = [ /* 위와 동일 */ ];
const filters = [{"XD_A":"XDR"}, {"XD_B_1":"38"}];
const filter = (data, filters) => {
return data.filter(e => {
try {
filters.forEach(o => {
Object.keys(o).forEach(key => {
// 값이 하나라도 일치하지 않으면 에러를 던져 검사 종료
if (e[key] !== o[key]) throw new Error();
});
});
return true; // 모든 조건을 통과한 경우만 포함
} catch {
return false; // 하나라도 불일치하면 제외
}
});
};
console.info(filter(data, filters));
동작 원리는 간단합니다. 각 객체에 대해 필터 조건을 순서대로 검사하다가, 단 하나의 키-값이라도 일치하지 않으면 에러를 던져 내부 반복문을 즉시 탈출하고 해당 객체를 결과에서 제외합니다. 모든 조건을 무사히 통과한 객체만 true를 반환해 새 배열에 포함됩니다.
실행 결과
콘솔에는 다음과 같이 조건을 만족하는 두 객체만 출력됩니다.
[
{ XD_A: 'XDR', XD_B_1: '38', XD_B_2: 'PB' },
{ XD_A: 'XDR', XD_B_1: '38', XD_B_2: 'PB' }
]
더 깔끔한 대안: every() 활용하기
try/catch와 throw를 조합한 방식은 사실상 반복문을 중간에 빠져나가기 위한 편법에 가깝습니다. Array.prototype.every()를 사용하면 같은 로직을 훨씬 직관적으로 표현할 수 있습니다.
const filter = (data, filters) => {
return data.filter(item =>
filters.every(cond =>
Object.keys(cond).every(key => item[key] === cond[key])
)
);
};
every()는 콜백이 하나라도 거짓을 반환하면 즉시 순회를 멈추고 false를 반환하므로, 불필요한 비교를 줄여 성능 면에서도 유리합니다. 코드의 의도도 "모든 조건을 만족하는가?"라는 질문 그대로 드러나 가독성이 크게 향상됩니다.
마무리
여러 조건으로 객체 배열을 필터링할 때는 filter()와 every()의 조합이 가장 깔끔하고 안전한 선택입니다. 조건이 복잡해질수록 명령형 반복문이나 예외 기반 흐름 제어보다 선언적인 고차 함수를 활용하는 것이 유지보수에도 큰 도움이 됩니다.