JSON = JavaScript Object Notation
데이터 교환을 위해 만들어진 객체 형태의 포맷
JSON.stringify : 객체를 JSON으로 변환
⇒ stringify하는 과정을 직렬화(serialize)라고 함
JSON.parse : JSON을 객체로 변환
⇒ JSON.parse 적용 과정은 역직렬화(deserialize)라고 함
JSON은 JS객체와 비슷해보이지만 미묘하게 다름
이 과제는 위 JSON 파트에서 배운 JSON.stringify라는 매서드가 어떻게 작동하는지 하나하나 구현해보는 것이 목적이다.
function stringifyJSON(obj) {
//undefined나 function의 경우 undefined
if (typeof obj === "undefined" || typeof obj === "function") {
return undefined;
}
//문자열인 경우
//문자열은 그냥 템플릿 리터럴에 넣어주면 원하는 결과가 안 나왔음
//이스케이프 표현을 사용해 '\"'이런식으로 좌우에 넣어줘야 했음
if (typeof obj === "string") {
return '"' + obj + '"';
}
//배열이 들어왔을 때 (재귀 함수 이용)
if (Array.isArray(obj)) {
const result = [];
if (obj.length === 0) {
return "[]";
//빈 배열일 때는 빈 배열에 쌍따옴표 씌운 형태 반환
} else {
for (let i = 0; i < obj.length; i++) {
result.push(stringifyJSON(obj[i]));
//빈 배열이 아닐 때는 반복문을 돌리는데
//각 요소를 다시 재귀함수의 전달인자로 넣어준다
//일차원 배열이 아니었을 경우, 즉 다차원 배열인 경우에는 배열 속의 배열의 요소들을 또 다시 문자열로 만들어주어야 하므로 이 과정이 필요함
//배열 속에 배열 외에도 갤체가 들어있는 경우에는
//아래쪽 객체 조건문에 걸려서 객체도 문자열로 하나하나 다 바꿔줄 수 있음
}
return "[" + result + "]";
}
}
//객체 형태가 들어왔을 때
if (typeof obj === "object" && obj !== null) {
let result = "";
// 객체인데 null값이 아닌 경우 (null값 처리를 여기서 해줌)
//null은 typeof를 할 경우 object로 나오기 때문에 !
for (let key in obj) {
if (typeof obj[key] === "function" || obj[key] === undefined) {
//빈 객체인 경우
return "{}";
}
// 빈 객체가 아닌 경우
//key를 재귀함수에 돌려서 문자열로 만들어주고 json이라는 변수에 할당
//key에 해당하는 값도 재귀함수에 돌려서 문자열로 만들어 준다
//처음부터 객체 속으로 이 값들을 넣어주지 않고
//위쪽에서 선언해둔 빈 문자열 result에 해당 값들을 원하는 모양대로 배치해줌
let json = stringifyJSON(key);
obj[key] = stringifyJSON(obj[key]);
result = result + json + ":" + obj[key];
result = result + ",";
}
result = result.slice(0, -1);
//맨 마지막 콤마는 잘라내고 나머지 값들만 남겨서 중괄호 속에 넣어준다
return "{" + result + "}";
}
return String(obj);
//위의 조건들 외에 남은 케이스들은 모두 문자열로 바꿔주는 String()함수 사용
}
Tree UI는 화면을 구성할 때 재귀를 사용하는 가장 대표적인 예시이므로 직접 코드를 작성하면서 재귀에 좀 더 친숙해지도록 하는 과제였음.
위의 이미처럼 음료, 음식, 굿즈, 카드라는 큰 노드 아래에 각각 다른 작은 노드들이 존재하고, 각 노드들은 자식 노드를 가지고 있는 경우에만 체크박스를 가지고 있다.
오랜만에 DOM을 사용하려니 살짝 머리가 아팠지만, 페어님과 함께 차근차근 풀다보니 오히려 앞의 JSON.stringify 과제보다도 빠르게 끝낼 수 있었다.
반복문이나 조건문을 생각하기 이전에 우선 기억을 더듬어서 어떻게 DOM을 사용해 li, checkbox, span 등의 엘리먼트들을 만들지 생각해보았다.
const root = document.getElementById("root");
//html 파일에는 가장 상위의 div에 해당하는 root 변수가 선언&할당되어 있었음.
function createTreeView(menu, currentNode) {
//함수는 메뉴와 현재 위치한 노드를 전달 인자로 받는다
//list
const li = document.createElement("li");
currentNode.append(li);
//checkbox
const input = document.createElement("input");
input.type = "checkbox";
li.append(input);
//span
const span = document.createElement("span");
li.textContent = item.name;
li.append(span);
}
createTreeView(menu, root);
트리 구조는 menu라는 배열 속에 있는 객체의 개수만큼 반복되어 만들어져야 하므로 배열을 순회하는 반복문을 만들어 준다.
for (let item of menu) {
if (item.children === undefined) {
//테스트에서 자식 노드가 없는 경우에는 li 엘리먼트 안에 단순히 이름(name)만 표시하라고 했으므로 li 엘리먼트 외 나머지를 모두 지워줌.
const li = document.createElement("li");
currentNode.append(li);
li.textContent = item.name;
} else {
//자식 노드가 있는 경우에는 li,input, span 모두 존재!
const li = document.createElement("li");
currentNode.append(li);
//checkbox
const input = document.createElement("input");
input.type = "checkbox";
li.append(input);
//span
const span = document.createElement("span");
span.textContent = item.name;
li.append(span);
//childrenUl을 만들어주는 이유는
//가장 상위의 노드는 root가 이미 작성되어 있어서 맨 처음 매개변수currentNode에 root를 전달 받으면 append 할 수 있지만,
//재귀함수로 그 아래의 노드들을 만들어줄 때는 root에 append할 것이 아니라 새로운 ul을 만들어준 뒤 append 해주어야 하기 때문.
const childrenUl = document.createElement("ul");
li.append(childrenUl);
createTreeView(item.children, childrenUl);
}
}
createTreeView(menu, root);
재귀의 base case가 재귀 함수를 구현할 때 재귀의 탈출 조건(재귀 호출이 멈추는 조건)을 구성한다.
객체를 문자열로 변환하기 위해 메서드(message.toString())나 형변환(String(message))을 시도하면, [object Object] 라는 결과를 리턴한다.
JSON.stringify : 객체를 JSON으로 변환하고 이때 type은 string
JSON.parse : JSON을 객체로 변환하고 이때 type은 object