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

JavaScript 배열에서 부모 ID 기준으로 모든 자식 ID 목록 생성하기

JavaScript로 계층형 데이터를 다룰 때, 각 객체가 자신의 직접 및 간접적인 하위 항목(자식)을 모두 담고 있어야 하는 경우가 자주 있습니다. 이번 글에서는 idparent_id로 구성된 배열을 가공하여, 각 객체에 childs 속성을 추가하는 방법을 알아보겠습니다.

문제 상황

다음과 같이 카테고리 정보를 담은 객체 배열이 있다고 가정해 보겠습니다.

const arr = [
    { id: 1, parent_id: 0, title: 'Movies' },
    { id: 2, parent_id: 0, title: 'Music' },
    { id: 3, parent_id: 1, title: 'Russian movies' },
    { id: 4, parent_id: 2, title: 'Russian music' },
    { id: 5, parent_id: 3, title: 'New' },
    { id: 6, parent_id: 3, title: 'Top10' },
    { id: 7, parent_id: 4, title: 'New' },
    { id: 8, parent_id: 4, title: 'Top10' },
    { id: 9, parent_id: 0, title: 'Soft' }
];

여기서 요구되는 것은 이 배열을 입력받아, 각 객체에 새로운 속성 childs를 추가한 새로운 배열을 반환하는 함수입니다. 이 childs 속성에는 해당 객체의 직접적인 자식뿐만 아니라 간접적인 자식(손자녀까지)의 모든 id가 배열 형태로 담겨야 합니다.

기대 결과

예를 들어 id가 1인 'Movies'는 직접 자식인 3번뿐 아니라, 3번의 자식인 5번과 6번도 포함해야 하므로 최종 출력은 다음과 같습니다.

[
    { id: 1, parent_id: 0, title: 'Movies', childs: [ 3, 5, 6 ] },
    { id: 2, parent_id: 0, title: 'Music', childs: [ 4, 7, 8 ] },
    { id: 3, parent_id: 1, title: 'Russian movies', childs: [ 5, 6 ] },
    { id: 4, parent_id: 2, title: 'Russian music', childs: [ 7, 8 ] },
    { id: 5, parent_id: 3, title: 'New', childs: [] },
    { id: 6, parent_id: 3, title: 'Top10', childs: [] },
    { id: 7, parent_id: 4, title: 'New', childs: [] },
    { id: 8, parent_id: 4, title: 'Top10', childs: [] },
    { id: 9, parent_id: 0, title: 'Soft', childs: [] }
]

구현 코드

이제 이 기능을 수행하는 함수를 작성해 보겠습니다. reduce()forEach()를 활용하면 간결하게 구현할 수 있습니다.

const arr = [
    { id: 1, parent_id: 0, title: 'Movies' },
    { id: 2, parent_id: 0, title: 'Music' },
    { id: 3, parent_id: 1, title: 'Russian movies' },
    { id: 4, parent_id: 2, title: 'Russian music' },
    { id: 5, parent_id: 3, title: 'New' },
    { id: 6, parent_id: 3, title: 'Top10' },
    { id: 7, parent_id: 4, title: 'New' },
    { id: 8, parent_id: 4, title: 'Top10' },
    { id: 9, parent_id: 0, title: 'Soft' }
];
const generateChild = arr => {
    return arr.reduce((acc, val, ind, array) => {
        const childs = [];
        array.forEach((el, i) => {
            if(childs.includes(el.parent_id) || el.parent_id === val.id){
                childs.push(el.id);
            };
        });
        return acc.concat({...val, childs});
    }, []);
};
console.log(generateChild(arr));

코드 동작 원리

이 코드의 핵심 로직은 다음과 같습니다.

1. reduce()로 순회: 배열의 각 객체를 순서대로 처리하면서 결과 누적 배열(acc)을 만듭니다.

2. 자식 탐색 조건: 내부 forEach에서 각 요소의 parent_id가 현재 객체의 id와 일치하면 직접 자식으로 판단하여 추가합니다.

3. 간접 자식 포함: 이미 childs 배열에 등록된 id를 parent로 가지는 요소 역시 자식으로 추가됩니다. 즉, childs.includes(el.parent_id) 조건 덕분에 손자녀 관계까지 재귀적으로 반영됩니다.

4. 스프레드 연산자로 병합: {...val, childs}를 통해 기존 객체의 속성을 그대로 유지하면서 childs 속성만 추가합니다.

실행 결과

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

[
    { id: 1, parent_id: 0, title: 'Movies', childs: [ 3, 5, 6 ] },
    { id: 2, parent_id: 0, title: 'Music', childs: [ 4, 7, 8 ] },
    { id: 3, parent_id: 1, title: 'Russian movies', childs: [ 5, 6 ] },
    { id: 4, parent_id: 2, title: 'Russian music', childs: [ 7, 8 ] },
    { id: 5, parent_id: 3, title: 'New', childs: [] },
    { id: 6, parent_id: 3, title: 'Top10', childs: [] },
    { id: 7, parent_id: 4, title: 'New', childs: [] },
    { id: 8, parent_id: 4, title: 'Top10', childs: [] },
    { id: 9, parent_id: 0, title: 'Soft', childs: [] }
]

참고 사항

위 구현은 이중 반복문을 사용하므로 시간 복잡도는 O(n²)입니다. 데이터 양이 매우 크다면, 먼저 parent_id → 자식 id 목록 형태의 맵(Map 또는 객체)을 한 번의 순회로 만든 뒤, 이를 참조하는 방식으로 성능을 개선할 수 있습니다. 또한 실무에서는 오타 가능성이 있는 'childs' 대신 표준적인 영문 표현인 'children'을 속성명으로 사용하는 것이 좋습니다.