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

JavaScript에서 키를 기준으로 두 객체 배열 결합하는 방법

개요

JavaScript로 개발을 하다 보면 자식(child) 데이터와 부모(parent) 데이터가 각각 별도의 배열로 존재하는 경우가 많습니다. 이럴 때 특정 키(예: id)를 기준으로 두 배열을 하나의 구조로 결합해야 하는 일이 자주 발생합니다. 이번 글에서는 이러한 작업을 수행하는 방법을 예제와 함께 살펴보겠습니다.

문제 상황

다음과 같이 자식 객체 배열과 부모 객체 배열이 있다고 가정해 보겠습니다.

const child = [{
    id: 1,
    name: 'somename',
    parent: {
        id: 2
    },
},
{
    id: 2,
    name: 'some child name',
    parent: {
        id: 4
    }
}];
const parent = [{
    id: 1,
    parentName: 'The first',
    child: {}
},
{
    id: 2,
    parentName: 'The second',
    child: {}
},
{
    id: 3,
    parentName: 'The third',
    child: {}
},
{
    id: 4,
    parentName: 'The fourth',
    child: {}
}];

여기서 자식 객체의 parent.id 값은 해당 자식이 속한 부모 객체의 id를 가리킵니다. 즉, 첫 번째 자식(id: 1)은 부모 id 2에 속하고, 두 번째 자식(id: 2)은 부모 id 4에 속합니다.

우리가 작성할 함수는 이 두 배열을 인수로 받아, 각 자식 객체를 대응하는 부모 객체의 child 속성에 병합해야 합니다. 따라서 최종 출력 결과는 다음과 같아야 합니다.

const output = [
    {
        id: 1,
        parentName: 'The first',
        child: {}
    },
    {
        id: 2,
        parentName: 'The second',
        child: {
            id: 1,
            name: 'somename'
        }
    },
    {
        id: 3,
        parentName: 'The third',
        child: {}
    },
    {
        id: 4,
        parentName: 'The fourth',
        child: {
            id: 2,
            name: 'some child name'
        }
    }
];

구현 방법

이 문제를 해결하는 코드는 다음과 같습니다.

const combineParentChild = (parent, child) => {
    const combined = [];
    for (let i = 0; i < parent.length; i++) {
        for (let j = 0; j < child.length; j++) {
            if (child[j].parent.id === parent[i].id) {
                parent[i].child.id = child[j].id;
                parent[i].child.name = child[j].name;
                break;
            }
        }
        combined.push(parent[i]);
    }
    return combined;
};
console.log(combineParentChild(parent, child));

코드 설명

  • 외부 반복문은 부모 배열을 순회하고, 내부 반복문은 자식 배열을 순회합니다.
  • 자식의 parent.id와 부모의 id가 일치하면, 자식의 정보를 부모 객체의 child 속성에 할당합니다.
  • break 문으로 일치하는 첫 번째 자식만 처리한 뒤 내부 반복문을 종료합니다.
  • 처리된 부모 객체를 combined 배열에 추가하고, 최종적으로 이 배열을 반환합니다.

실행 결과

콘솔에 출력되는 결과는 다음과 같습니다.

[
    { id: 1, parentName: 'The first', child: {} },
    {
        id: 2,
        parentName: 'The second',
        child: { id: 1, name: 'somename' }
    },
    { id: 3, parentName: 'The third', child: {} },
    {
        id: 4,
        parentName: 'The fourth',
        child: { id: 2, name: 'some child name' }
    }
]

더 나은 방법: Map을 활용한 O(n) 접근

위 방식은 이중 반복문을 사용하므로 시간 복잡도가 O(n × m)입니다. 데이터 양이 커지면 성능이 눈에 띄게 저하될 수 있습니다. Map을 활용하면 시간 복잡도를 O(n + m)으로 줄일 수 있습니다.

const combineParentChild = (parent, child) => {
    // 자식 객체를 부모 id 기준으로 그룹화
    const childMap = new Map();
    child.forEach(c => {
        if (!childMap.has(c.parent.id)) {
            childMap.set(c.parent.id, []);
        }
        childMap.get(c.parent.id).push(c);
    });

    return parent.map(p => ({
        ...p,
        child: childMap.get(p.id)?.[0] ?? p.child ?? {}
    }));
};

이 방식은 각 배열을 한 번씩만 순회하므로 대용량 데이터에서도 효율적입니다. 또한 원본 배열을 직접 수정하지 않고 새로운 객체를 생성하므로, 불변성(immutability)을 유지할 수 있다는 추가적인 장점도 있습니다.

마무리

키 값을 기준으로 두 객체 배열을 결합하는 것은 실무에서 매우 흔히 마주치는 작업입니다. 데이터 크기가 작다면 단순한 이중 반복문으로도 충분하지만, 성능이 중요하거나 데이터가 많은 경우에는 Map 기반의 조회 방식을 사용하는 것이 바람직합니다.