* 재귀를 이용해 메서드 JSON.stringify를 함수의 형태로 직접 구현하기
- JSON.stringify는 객체를 JSON으로 변환하는 메서드이다.
- 이 메서드를 재귀를 사용하여 함수의 형태로 직접 구현해본다.
- 자바스크립트 객체와 JSON은 대표적인 트리 구조로 되어 있다.
- 즉, 전형적인 재귀 탐색이 가능한 구조(객체의 값으로 객체를 포함하는 구조)이기 때문에 재귀 사용을 적극 권장한다.
JSON.stringify
아래의 JSON.stringify와 같은 결과를 구현하는 함수를 만들어본다.
JSON.stringify 규칙
[Browser에 존재하는 JSON.stringfy 함수 === 구현할 stringifyJSON]
JSON.stringfy 함수는 input 값을 JSON 형식으로 변환한다.
1. 단, undefined와 function은 JSON으로 생략되거나 null로 변환된다.
2. stringifyJSON은 아래와 같이 작동한다.
- Boolean이 input으로 주어졌을 경우 : stringifyJSON(true); // 'true'
- String이 input으로 주어졌을 경우 : stringifyJSON('foo'); // '"foo"'
- Array가 input으로 주어졌을 경우 : stringifyJSON([1, 'false', false]); // '[1,"false",false]'
- Object가 input으로 주어졌을 경우 : stringifyJSON({ x: 5 }); // '{"x":5}'
- undefined, function이 주어졌을 경우 :
stringifyJSON(undefined) // undefined
stringifyJSON(function(){}) // undefined
stringifyJSON({ x: undefined, y: function(){} }) // '{}'
그 전에 알아야하는 것들
1. 백틱 용례 (중요!!)
- 백틱 속에서 ${} 안의 []나 ''나 "", [''] 등의 기호는 사라진다.
- [' "" '] -> [''] 속의 ""는 살아있다.
- 객체의 경우 백틱을 사용해 바로 문자열로 변환하면 '[object Object]'가 나온다.
2. 객체 속 undefined와 function 결과
3. if 조건에 없으면 함수 결과는 undefined
- 함수에 undefined, function이 주어졌을 경우를 이렇게 처리해주면 된다.
구현결과
1. 처음에 구현한 stringiftJSON
function stringifyJSON(obj) {
if(obj === null){ // 🟡 null (typeof null === 'object')
return 'null'; // 'null'
}
if (typeof obj === "number"){ // 🟡 9 (숫자)
return String(obj); // '9'
}
if(typeof obj === "boolean"){ // 🟡 true (Boolean)
return String(obj); // 'true'
}
if(typeof obj === "string"){ // 🟡 'apple' (문자열)
return `"${obj}"` // '"apple"'
}
if(Array.isArray(obj)){ // 🟡 [7, 'hi'] (배열)
let newArr = [];
obj.forEach(function (ele){
newArr.push(stringifyJSON(ele)) // ['7', '"hi"']
})
return `[${newArr}]` // [7, "hi"]
}
if(typeof obj === "object"){ // 🟡 { age: 10, name: 'S' } (객체)
let result = "";
for(let key in obj) {
if(obj[key] === undefined || typeof obj[key] === "function"){
result = String(result); // String("") === '' // 빈문자열(띄어쓰기X)
} else {
result += `${stringifyJSON(key)}:${stringifyJSON(obj[key])},`;
// 또는 stringifyJSON(key) + ':' + stringifyJSON(obj[key]) + ',';
}
}
// (1) 첫 번째 result = `${'"age"'}:${'10'},`
// (1) 즉, result = '"age":10,'
// (1) 두 번째 result = '"age":10,' + `${'"name"'}:${'"s"'},`
// (2) 즉, result = '"age":10,"name":"s",' // 📌
result = result.substr(0, result.length -1); // 마지막에 있는 콤마 삭제 (또는 result.slice(0, -1))
return `{${result}}`
// 최종 result = '{"age":10,"name":"s"}'
}
};
2. switch를 사용한 리팩토링
그 전에 알아야 하는 join 용법
const elements = [3, 1, 2];
console.log(elements.join()); // Expected output: "3,1,2"
console.log(elements.join('')); // Expected output: "312"
console.log(elements.join('-')); // Expected output: "3-1-2"
// const elements = ["3, 1, 2"];
console.log(elements.join()); // Expected output: "3, 1, 2"
// const elements = ["3, '1', 2"];
console.log(elements.join()); // Expected output: "3, '1', 2"
console.log(['"age":10'].join(',')) // Expected output: '"age":10'
console.log([['"age":10', '"age":13'].join(',')].join(','))
// Expected output: '"age":10,"age":13'
리팩토링한 코드
function stringifyJSON(obj) {
if (obj === null) {
return 'null';
}
// 🟣 바뀐 부분 (if문 대신 switch문 사용)
switch (typeof obj) {
case 'number': // typeof obj === "number" 과 동일
case 'boolean':
return String(obj); // 또는 `${obj}`
case 'string':
return `"${obj}"`;
}
// 🟣 바뀐 부분 (map과 join 사용) // 🟡 [7, 'hi'] (배열)
if (Array.isArray(obj)) {
return `[${obj.map(stringifyJSON).join(",")}]`; // map 쓰면 ['7', '"hi"'] --> join 쓰면 '7,"hi"' --> 결과 '[7,"hi"]'
}
// 🟣 바뀐 부분 (continue, join 사용) // 🟡 {age: 10} (객체)
if (typeof obj === 'object') {
let result = [];
for (let key in obj) {
if (obj[key] === undefined || typeof obj[key] === 'function') {
continue; // 이 경우, 무시한다.
}
result.push(`${stringifyJSON(key)}:${stringifyJSON(obj[key])}`);
} // (1-1) result = [`${'"age"'}:${'10'}`]
// (1-2) result = ['"age":10'] // 만약 여러 개라면 ['"age":10', '"age":13']
return `{${result.join(",")}}`;
// `{${'"age":10'}}`; // 만약 여러 개라면 `{${'"age":10,"age":13'}}`;
// '{"age":10}'; // 만약 여러 개라면 '{"age":10,"age":13}';
}
};
- if문 대신 switch문 사용: 데이터 타입에 따라 다른 동작을 수행하는 경우 switch문을 사용하는 것이 보다 간결하고 가독성이 좋다.
- 객체 속성 값이 undefined나 함수일 때 result에 추가하지 않고 넘어감: 이러한 값을 JSON으로 직렬화할 수 없으므로 추가할 필요가 없다. (리팩토링 전에는 빈문자열 활용)
- result를 문자열 대신 배열로 초기화: 문자열과 문자열 연결 대신 Array.push와 Array.join을 사용하여 마지막에 ,를 빼지 않아도 된다.
백틱이 헷갈리는 경우
위의 경우를 참고하여 아래와 같은 코드를 작성해줘도 된다.
// (1) 배열
if (Array.isArray(obj)) {
let str = '[';
for (let i=0 ; i < obj.length ; i+=1) {
if ( i === obj.length -1) { str += stringifyJSON(obj[i]); }
else { str += stringifyJSON(obj[i]) + ','; }}
str += ']'
return str;
}
// (2) 객체 : 이런 식으로 변수를 만들어 두면 가독성이 좋아진다.
let keyJSON = stringifyJSON(key);
vlet valueJSON = stringifyJSON(obj[key]);
res.push(keyJSON + ‘:’ + valueJSON);
return ‘{‘ + res + ‘}’;
결과 확인
잘 된다.
테스트 코드
생략
더보기
const stringifiableObjects = [
9,
null,
true,
false,
"Hello world",
[],
[8],
["hi"],
[8, "hi"],
[1, 0, -1, -0.3, 0.3, 1343.32, 3345, 0.00011999],
[8, [[], 3, 4]],
[[[["foo"]]]],
{},
{ a: "apple" },
{ foo: true, bar: false, baz: null },
{ "boolean, true": true, "boolean, false": false, null: null },
// basic nesting
{ a: { b: "c" } },
{ a: ["b", "c"] },
[{ a: "b" }, { c: "d" }],
{ a: [], c: {}, b: true },
];
// JSON은 function과 undefined를 stringify해서는 안 된다.
const unstringifiableValue = {
functions: function () {},
undefined: undefined,
};
describe("stringifyJSON는 JSON.stringify 함수를 실행했을 때와 같은 결과를 리턴해야 합니다.", function () {
stringifiableObjects.forEach(function (test) {
const stringified = JSON.stringify(test)
it(`객체는 문자열 "${stringified}"로 변환되어야 합니다`, function (done) {
const expected = stringified;
const result = stringifyJSON(test);
expect(result).to.equal(expected);
done();
});
});
it("함수와 undefined는 stringify되지 않습니다.", function (done) {
const expected = JSON.stringify(unstringifiableValue);
const result = stringifyJSON(unstringifiableValue);
expect(result).to.equal(expected);
done();
});
});
백틱에 대해서 정말 제대로 알게 된 날!
하! 하!
728x90
'FE > JavaScript' 카테고리의 다른 글
[JS] 하노이의 탑 (0) | 2023.04.20 |
---|---|
[JS] 재귀를 이용하여 Tree UI 구현하기 (0) | 2023.04.12 |
[JS] JSON 개요 (JavaScript Object Notation) (0) | 2023.04.12 |
[JS] 재귀 (0) | 2023.04.11 |
[JS] fetch를 이용한 네트워크 요청 - 2/2 (활용) (0) | 2023.03.27 |