Computer >> 컴퓨터 >  >> 프로그래밍 >> JavaScript

JavaScript로 두 개의 서로 다른 객체 배열을 병합하는 방법 – map()과 filter() 활용하기

문제 상황

JavaScript 개발을 하다 보면 두 개 이상의 배열을 특정 키 값을 기준으로 하나로 합쳐야 하는 경우가 자주 발생합니다. 예를 들어 설문조사 데이터를 처리할 때, 한 배열에는 응답자 정보가, 다른 배열에는 각 질문에 대한 선택지 정보가 담겨 있고, 이를 질문 번호를 기준으로 하나의 구조로 묶어야 할 수 있습니다.

다음과 같이 두 개의 객체 배열이 있다고 가정해 보겠습니다. 첫 번째 배열(arr1)은 여러 사람이 답변한 질문 정보를, 두 번째 배열(arr2)은 각 질문에 해당하는 선택지 정보를 담고 있습니다.

const arr1 = [
  { personalID: '11', questionNumber: '1', value: 'Something' },
  { personalID: '12', questionNumber: '2', value: 'whatever' },
  { personalID: '13', questionNumber: '3', value: 'anything' },
  { personalID: '14', questionNumber: '4', value: 'null' }
];

const arr2 = [
  { questionNumber: '2', chID: '111', cValue: 'red' },
  { questionNumber: '2', chID: '112', cValue: 'green' },
  { questionNumber: '2', chID: '113', cValue: 'blue' },
  { questionNumber: '3', chID: '114', cValue: 'yellow' },
  { questionNumber: '4', chID: '115', cValue: 'red' }
];

여기서 작성해야 할 함수는 두 배열의 데이터를 고유한 응답자(personalID)별로 그룹화하여, 각 질문과 그에 딸린 선택지(choice)를 하나의 객체로 표현하는 것입니다.

원하는 출력 결과

const output = [
  {
    personalID: '11',
    questionNumber: '1',
    value: 'Something'
  },
  {
    personalID: '12',
    questionNumber: '2',
    value: 'whatever',
    choice: [
      { questionNumber: '2', chID: '111', cValue: 'red' },
      { questionNumber: '2', chID: '112', cValue: 'green' },
      { questionNumber: '2', chID: '113', cValue: 'blue' }
    ]
  },
  {
    personalID: '13',
    questionNumber: '3',
    value: 'anything',
    choice: [
      { questionNumber: '3', chID: '114', cValue: 'yellow' }
    ]
  },
  {
    personalID: '14',
    questionNumber: '4',
    value: 'null',
    choice: [
      { questionNumber: '4', chID: '115', cValue: 'red' }
    ]
  }
];

map()과 filter()를 활용한 해결 방법

가장 깔끔하고 가독성이 좋은 방법은 map()으로 arr1을 순회하면서, 각 객체의 questionNumber와 일치하는 항목만 filter()로 골라내어 새로운 choice 속성으로 추가하는 것입니다.

const mergeArray = (arr1 = [], arr2 = []) => {
  return arr1.map(obj => {
    const choice = arr2.filter(
      item => item.questionNumber === obj.questionNumber
    );
    return choice.length ? { ...obj, choice } : { ...obj };
  });
};

console.log(JSON.stringify(mergeArray(arr1, arr2), undefined, 4));

실행 결과

콘솔에는 아래와 같은 결과가 출력됩니다.

[
  {
    "personalID": "11",
    "questionNumber": "1",
    "value": "Something"
  },
  {
    "personalID": "12",
    "questionNumber": "2",
    "value": "whatever",
    "choice": [
      { "questionNumber": "2", "chID": "111", "cValue": "red" },
      { "questionNumber": "2", "chID": "112", "cValue": "green" },
      { "questionNumber": "2", "chID": "113", "cValue": "blue" }
    ]
  },
  {
    "personalID": "13",
    "questionNumber": "3",
    "value": "anything",
    "choice": [
      { "questionNumber": "3", "chID": "114", "cValue": "yellow" }
    ]
  },
  {
    "personalID": "14",
    "questionNumber": "4",
    "value": "null",
    "choice": [
      { "questionNumber": "4", "chID": "115", "cValue": "red" }
    ]
  }
]

코드 동작 원리

  • map(): arr1의 모든 요소를 순회하며 새로운 객체를 반환합니다. 원본 배열은 변경되지 않습니다.
  • filter(): arr2 중에서 현재 객체의 questionNumber와 일치하는 선택지만 추출합니다.
  • 스프레드 연산자(...): 기존 객체의 속성을 그대로 유지하면서 choice 배열만 추가합니다.

대용량 데이터를 위한 성능 최적화

위 방식은 직관적이지만, 두 배열의 크기가 커질수록 시간 복잡도가 O(n×m)으로 증가해 성능이 저하될 수 있습니다. 데이터가 많다면 Map을 사용해 questionNumber별로 미리 그룹화한 뒤 병합하면 O(n+m)으로 개선할 수 있습니다.

const mergeArrayOptimized = (arr1 = [], arr2 = []) => {
  const grouped = new Map();
  arr2.forEach(item => {
    const list = grouped.get(item.questionNumber) || [];
    list.push(item);
    grouped.set(item.questionNumber, list);
  });

  return arr1.map(obj => {
    const choice = grouped.get(obj.questionNumber) || [];
    return choice.length ? { ...obj, choice } : { ...obj };
  });
};

마무리

두 배열을 특정 키 기준으로 병합할 때는 map()filter() 조합이 코드를 명확하고 안전하게 만들어 줍니다. 데이터 양이 많아지면 Map 기반 인덱싱으로 성능을 개선할 수 있으며, 무엇보다 원본 배열을 수정하지 않으면서 원하는 구조의 새로운 배열을 만드는 것이 핵심입니다.