본문 바로가기

프로그래밍

[Javascript]중첩 객체에서 원하는 객체 찾기

문제

Javascript에서 API를 통해 JSON을 전달받았는데, 아래와 같이 속성 안에 객체가 재귀적으로 존재하는 중첩 객체(객체 깊이는 계속 깊어질 수 있음)를 전달받아 그 안에서 원하는 속성 값을 갖는 자식 노드를 찾아야 하는 문제가 있어서 이 부분에 대해 고민했던 내용을 작성하고자 합니다.

const testObj = {
    a: 1,
    b: {
        c:2,
        d:"hello",
        e:{
            a:3,
            d:"hi"
        }
    }
};

//요청 : a=3인 객체를 찾아 반환하시오.

DFS를 이용

우선 제일 먼저 고려했던 것은 알고리즘에서 많이 사용하는 DFS/BFS를 사용하는 방법입니다. 아무래도 취업준비를 했을 때 상당히 많이 접했던 문제 풀이법이기도 했고, 무한히 중첩될 수 있던 객체여서 브루트포스 방식을 사용하여 일일이 비교할 수도 없었습니다.

다만, javascript 객체를 이용한 DFS는 조금 더 까다로웠습니다. 왜냐면, 일반적으로 DFS는 모든 형식이 동일한 트리 노드가 존재하고 있을 때 노드를 타고 내려가며 검색을 하는 방식이었는데 javascript의 객체는 안에 들어가는 속성 값의 제약이 존재하지 않습니다. 따라서 무작정 자식 속성의 자식을 타고 가는 방법이 아닌 자식이 객체일 때만 골라서 DFS를 실행해야 합니다.

let answer;

const DFS = (obj, name, val) =>
{
  if(obj[name] === val)
  {
    answer = obj;
  } 
  Object.values(obj).forEach(value=>{
    if(typeof value === 'object')
      DFS(value, name, val);
  })
}

DFS(testObj, "d", "hi")
console.log('answer ',answer);

DFS를 사용한 결과
DFS를 사용한 결과

위와 같은 방식으로 DFS를 통해서 중첩 객체 속에서 원하는 객체를 얻을 수 있습니다. 하지만, 자바스크립트에서는 이런 식으로 명시적으로 재귀를 사용하지 않아도 특정 함수를 이용하면 알아서 재귀 과정을 실행시켜줄 수 있습니다. 바로 JSON.stringify()를 사용하는 방법입니다!

JSON.stringify()란?

JSON.stringify()는 보통 값을 스토리지에 저장하거나 API를 통해 서버와 클라이언트 간 데이터를 전송할 때 객체/배열/값을 문자열로 치환하기 위해 사용하는 함수입니다. 보통은 아래와 같이 변환하고 싶은 오브젝트만을 주고 실행하며, 결괏값으로는 전달한 오브젝트가 문자열로 변환되어 반환됩니다.

const testObj = {
  a: 1,
  b: {
  	c:2,
    d:"hello",
    e:{
    	a:3,
      d:"hi"
    }
  }
};

console.log(JSON.stringify(testObj));

JSON.stringify() 사용 결과
JSON.stringify() 사용 결과

별다른 처리 없이도 원시형 타입 이외의 객체/배열도 문자열로 잘 변환되는 걸 볼 수 있습니다.

 

이 JSON.stringify()는 사실 1개의 파라미터만 받는 것이 아닙니다. 총 3개의 파라미터를 전달받으며 구조는 아래와 같습니다.

JSON.stringify(object, [replacer, [space]])

  • object: 문자열로 변환하고 싶은 객체/값
  • replacer: stringify()를 사용하여 문자열로 변환하고 싶은 속성만 골라 선택적으로 문자열화 할 수 있는 함수입니다. undefined로 반환되지 않은 값을 문자열로 변환해줍니다.
  • space: 최종적으로 반환되는 문자열의 간격을 조절하는 역할을 합니다. 숫자(0~10), 문자(길이 10 이하, 초과 시 앞의 10개의 문자로 처리)를 넣을 수 있습니다.

JSON.stringify()를 이용한 중첩 객체 속 객체 찾기

이제 본론으로 넘어가서 JSON.stringify()를 이용하여 중첩 객체 속에서 원하는 속성 값을 갖는 객체를 찾기 위해서는 위의 stringify()에서 2번째 파라미터는 replacer를 사용하면 됩니다.

replacer는 (key, value)를 전달받아 값을 반환하는 함수로 JSON.stringify()로 전달된 object에서 원하는 속성 또는 값을 특정하여 문자열화하거나 문자열화에서 제외시킬 수 있습니다. 이때, return 된 value 안의 내용물은 차후에 다시 replacer에 의해 호출됩니다. 즉, returned value가 객체인 경우 해당 객체 안의 모든 속성에 대해서 replacer가 다시 실행되게 됩니다.

JSON.stringify() replacer에서 value를 출력한 결과
JSON.stringify() replacer에서 value를 출력한 결과

이렇게 재귀되는 것을 이용해서 replacer 내부에서 원하는 속성과 값을 지정해서 맞을 경우 해당 값을 저장하는 방식을 사용하면 DFS()와 같이 복잡하게 재귀를 사용하지 않아도 재귀와 같은 기능을 사용할 수 있습니다.

덧붙여 DFS를 사용할 때는 문자열로 인해 forEach 사용 시 문자 배열로 분리되어 'maximum call stack size exceeded' 오류가 발생하여 object인지 아닌지 검사해서 재귀를 실행해야 하지만, replacer를 사용하면 객체/배열에 대해서만 재귀를 실행하기 때문에 object 타입 여부를 검사하지 않아도 된다는 장점이 있습니다.

 

JSON.stringify + replacer를 사용한 방식은 아래와 같이 작성할 수 있습니다.

const testObj = {
  a: 1,
  b: {
  	c:2,
    d:"hello",
    e:{
    	a:[3,4],
      d:"hi"
    }
  }
};

let answer;

const parse = (name, val)=>{
  JSON.stringify(testObj, (_, value)=>{
    if(value[name] === val)
     answer = value;
    return value;
  })
}

parse('d', 'hi');
console.log('answer ',answer);

JSON.stringify()를 사용한 중첩 객체 내 탐색 결과
JSON.stringify()를 사용한 중첩 객체 내 탐색 결과