JSON.stringify 함수 구현

semin·2023년 6월 12일
0

section 3

목록 보기
2/5
post-thumbnail

목적: 재귀 파악

input 값을 JSON 형식으로 변환시키기.
undefined와 function은 JSON으로 생략시키거나
null로 변환할 것.

결과값 예시

  9 -> 9,
  true -> true,
  'Hello world' -> "'Hello world'",
  [1, 'false', false] -> '[1,"false",false]'
  { x: 5 } -> '{"x":5}'
function stringifyJSON(obj) {
  // 간단한 출력부터 구현하기.
  
  if (typeof obj === "boolean" || typeof obj === "number" || obj === null) return String(obj);
  // 그냥 문자열로 나오면 되는 애들끼리 묶었다.

  else if (typeof obj === "string") return `"${obj}"`;
  // 문자열로 한 번 감싸서 출력.

케이스 순서로 따지면 다음이 배열인데,
조건문 내부 코드가 간단한 것부터 작성해 본다.

undefined와 함수를 미리 처리해 준다.

else if (typeof obj === "undefined" || typeof obj === "function") return "undefined";
else if (Array.isArray(obj)) {
    let newArr = [];
    for (let item of obj) {
      newArr.push(stringifyJSON(item));
      // 재귀 돌면서 push
    }
    return `[${newArr}]`;// 문자열로 감싸서 출력
  }

배열 케이스 처리를 해 준다.
새 배열을 만든 후 반복문을 써야 계속 초기화가 되지 않는다...
왜요? 제가 그런 말도 안 되는 실수한 사람처럼 보이시나요?
-true-

  else if (typeof obj === "object" && obj !== null) {
    let str = "";
    for (let key in obj) {
      if (typeof obj[key] === "function" || obj[key] === undefined) {
        return "{}";
      }
      
      let newKey = stringifyJSON(key);
      // 재귀 돌아서 키 받기
      
      let newValue = stringifyJSON(obj[key]);
      // 다시 돌아서 선언된 키에 대한 값 받기
      
      str = str + newKey + ":" + newValue + ",";
      // 키: 값 형태 만들어 주고 쉼표로 분리
    }
    str = str.slice(0, -1); // 마지막 쉼표 삭제
    return `{${str}}`; // 감싸서 출력
  } else return undefined; // 예외 처리
}

객체 케이스와 else 처리.
이렇게 해서 테스트는 통과가 됐다.
하지만... 이러면 되겠지?
했던 부분이 안 돼서 수정하는 과정이 꽤 있었다.
약간 만만하게 본 듯...

재귀를 돌면서 키와 값을 따로 받아야 한다는 생각 자체를 못했다.
한번에 할 수 있는 방법은 없을까?


내용 추가.
테스트는 통과했지만 실제 동작과 다른 부분이 있어
셀프 코드 리뷰를 작성해 본다.

이전에 작성한 전체 코드. 주석을 제외했다.

function stringifyJSON(obj) {
  if (typeof obj === "boolean" || typeof obj === "number" || obj === null) {
    return String(obj);
  } else if (typeof obj === "string") {
    return `"${obj}"`;
  }
  else if (typeof obj === "undefined" || typeof obj === "function") {
    return "undefined";
  }
  else if (Array.isArray(obj)) {
    let newArr = [];
    for (let item of obj) {
      newArr.push(stringifyJSON(item));
    }
    return `[${newArr}]`;
  }
  else if (typeof obj === "object" && obj !== null) {
    let str = "";
    for (let key in obj) {
      if (typeof obj[key] === "function" || obj[key] === undefined) {
        return "{}";
      }
      let newKey = stringifyJSON(key);
      let newValue = stringifyJSON(obj[key]);
      str = str + newKey + ":" + newValue + ",";
    }
    str = str.slice(0, -1);
    return `{${str}}`;
  } else return undefined;
}
  1. 객체 속성 순서
    객체의 속성이 for...in 루프를 사용하여 순회되는데,
    이렇게 작성되면 객체 속성의 순서를 보장하지 않는다.
    실제로 JSON.stringify() 함수는 객체 속성을
    특정 순서로 직렬화하기 때문에 수정 필요.
  2. 빈 객체 처리
    typeof obj === "object" && obj !== null 조건에서
    함수나 undefined 속성이 있는 경우 "{}" 문자열을 반환한다.
    그러나 JSON.stringify() 함수는 이 경우에
    해당 속성을 제외한 채로 빈 객체로 직렬화한다. 수정 필요.
  3. undefined와 함수에 대한 처리
    undefined와 함수는 "undefined" 문자열로 처리되지만
    실제 함수는 이 케이스를 직접 지원하지 않는다.
    해당 속성을 제외한 채로 직렬화하므로 수정 필요.
  4. 배열도 더 간결한 방법이 있을 것. 메서드를 이용해 수정해 보면 좋겠다.

수정된 코드.

function stringifyJSON(obj) {
  if (obj === null) {
    return "null";
  }
  if (typeof obj === "string") {
    return `"${obj}"`;
  }
  if (typeof obj === "number" || typeof obj === "boolean") {
    return obj.toString();
  }
  if (Array.isArray(obj)) {
    const elements = obj.map((element) => stringifyJSON(element));
    return `[${elements.join(",")}]`;
  }
  if (typeof obj === "object") {
    const properties = Object.keys(obj)
      .map((key) => {
        const value = stringifyJSON(obj[key]);
        if (value !== undefined) {
          return `"${key}":${value}`;
        }
      })
      .filter((property) => property !== undefined);
    return `{${properties.join(",")}}`;
  }

  return undefined;
}
  1. 객체 속성을 순서대로 직렬화.
    1-1. Object.keys()를 사용하여 속성 순서를 고려
  2. 빈 객체 처리 부분을 실제 함수와 동일하게 처리.
    2-1. 빈 중괄호 {} 로 직렬화
  3. undefined와 함수에 대한 처리를 변경하여 제외.
    3-1. else (생략) 처리.
  4. 배열 처리에 map과 join 사용.
    map()으로 배열의 요소를 stringifyJSON() 함수에 전달 후
    결과를 elements 배열에 저장함.
    join()으로 배열의 요소를 쉼표로 구분, 문자열로 연결.
  5. 독립적인 if문 처리
    각 경우가 서로 독립적이고 상호간에 영향을 주지 않는다면, 이렇게 쓰는 게 가독성과 유지보수성을 향상시킨다.
    각 조건에 대한 처리를 명확하게 구분할 수 있으며,
    코드를 수정/추가에도 다른 조건에 영향을 주지 않음.

그외

  1. null 처리 변경
    이전 코드의 null 조건은 객체 케이스를 위한 조건문이기 때문에, 더 명시적이고 일관성 있는 처리를 위해 null에 대한 처리를 따로 추가했다.

0개의 댓글