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

자바스크립트로 중첩된 JSON 객체 깊이 탐색(Deep Search)하기

자바스크립트를 다루다 보면 여러 겹으로 중첩된 JSON 객체 안에서 특정 키(key)와 값(value)의 조합을 만족하는 객체를 찾아야 하는 경우가 종종 있습니다. 이 글에서는 재귀 함수를 활용해 임의의 깊이까지 객체를 탐색하고, 조건에 맞는 모든 객체를 배열로 수집하는 방법을 알아보겠습니다.

문제 상황

다음과 같이 객체 안에 객체가, 그리고 배열 안에 객체가 중첩된 JSON 구조가 있다고 가정해 보겠습니다.

const obj = {
    id: 1,
    title: 'hello world',
    child: {
        id: null,
        title: 'foobar',
        child: {
            id: null,
            title: 'i should be in results array'
        }
    },
    foo: {
        id: null,
        title: 'i should be in results array too!'
    },
    deep: [
        { id: null, value: 'yo' },
        { id: null, value: 'yo2' }
    ]
};

여기서 요구사항은 다음과 같습니다.

  • 첫 번째 인수로 탐색 대상 객체를 받습니다.
  • 두 번째 인수로 찾고자 하는 키 문자열, 세 번째 인수로 값 문자열을 받습니다.
  • 객체 전체를 뒤져 해당 키-값 쌍을 가진 모든 객체를 찾아 배열 형태로 반환합니다.

해결 접근 방식

중첩 깊이가 정해져 있지 않기 때문에 반복문만으로는 해결하기 어렵습니다. 따라서 재귀(recursion)를 사용하는 것이 가장 효율적입니다. 로직은 다음 순서로 구성됩니다.

  1. 탐색 대상이 null, false이거나 객체 타입이 아니면 즉시 종료합니다.
  2. 현재 객체에서 주어진 키의 값이 찾는 값과 일치하면, 그 객체 자체를 결과 배열에 추가합니다.
  3. Object.keys()로 객체의 모든 키를 얻은 뒤 각 속성 값을 대상으로 함수를 다시 호출합니다.
  4. 모든 탐색이 끝나면 수집된 객체들을 담은 배열을 반환합니다.

구현 예제

const obj = {
    id: 1,
    title: 'hello world',
    child: {
        id: null,
        title: 'foobar',
        child: {
            id: null,
            title: 'i should be in results array'
        }
    },
    foo: {
        id: null,
        title: 'i should be in results array too!'
    },
    deep: [
        { id: null, value: 'yo' },
        { id: null, value: 'yo2' }
    ]
};

const findObject = (obj = {}, key, value) => {
    const result = [];

    const recursiveSearch = (obj = {}) => {
        // 유효한 객체가 아니면 탐색 중단
        if (!obj || typeof obj !== 'object') {
            return;
        }

        // 키-값이 일치하면 현재 객체를 결과에 추가
        if (obj[key] === value) {
            result.push(obj);
        }

        // 모든 하위 속성에 대해 재귀 호출
        Object.keys(obj).forEach((k) => {
            recursiveSearch(obj[k]);
        });
    };

    recursiveSearch(obj);
    return result;
};

console.log(findObject(obj, 'id', null));

실행 결과

idnull인 모든 객체가 깊이와 관계없이 배열로 수집되어 출력됩니다.

[
    {
        id: null,
        title: 'foobar',
        child: {
            id: null,
            title: 'i should be in results array'
        }
    },
    {
        id: null,
        title: 'i should be in results array'
    },
    {
        id: null,
        title: 'i should be in results array too!'
    },
    {
        id: null,
        value: 'yo'
    },
    {
        id: null,
        value: 'yo2'
    }
]

코드 설명 및 참고 사항

타입 체크가 중요한 이유

typeof obj !== 'object' 조건은 문자열, 숫자 같은 원시 값이 재귀 호출에 들어오는 것을 막아줍니다. 또한 !obj 조건 덕분에 null이나 undefined에 대해 Object.keys()를 호출하면서 발생할 수 있는 오류를 예방할 수 있습니다.

배열도 함께 탐색됩니다

자바스크립트에서 배열 역시 typeof 결과가 'object'이기 때문에, 위 코드는 별도의 분기 처리 없이도 deep 배열 내부의 객체들까지 자동으로 탐색합니다.

주의할 점

이 방식은 부모 객체와 그 자식 객체가 동시에 조건을 만족하면 둘 다 결과에 포함됩니다. 위 예제에서 title: 'foobar'를 가진 객체와 그 내부의 자식 객체가 모두 반환된 이유입니다. 만약 가장 깊은 곳의 객체만 필요하다면, 일치 시점에 더 이상 하위로 탐색하지 않도록 로직을 수정하면 됩니다.

이처럼 재귀 함수 하나만으로 복잡하게 중첩된 JSON 데이터도 손쉽게 검색할 수 있습니다. API 응답 데이터에서 특정 조건의 노드를 찾거나, 설정 파일에서 특정 값을 추출할 때 유용하게 활용할 수 있는 패턴이니 꼭 기억해 두세요.