[구현] JSON.stringify( )를 재귀함수로 구현하기

JSON.stringify( )는 자바스크립트의 값을 JSON 문자열로 변환한다.

JSON이란?

JSON은 JavaScript Object Notation의 약자로, 브라우저와 서버사이에서 오고가는 데이터의 형식이다.


JSON.stringify(value, replacer, space)

value(필수): JSON 문자열로 변환할 값이다.(배열, 객체, 또는 숫자, 문자 등이 될 수 있다.)

replacer(선택): 함수 또는 배열이 될 수 있다. 이 값이 null 이거나 제공되지 않으면, 객체의 모든 속성들이 JSON 문자열 결과에 포함된다.

[함수일 때]

문자열화 프로세스의 작동을 변경하는 함수로 사용할 수 있다.

문자열화 될 key와 value, 2개의 매개변수를 받는다.

예제

function replacer(key, value) {
if (typeof value === ‘string’) {
return undefined;
}
return value;
}

var foo = {name: ‘jason’, nickname: ‘ball’, weight: 75};
var useJson = JSON.stringify(foo, replacer); 
console.log(useJson) // {"weight":75}

JSON.stringify()에서 replacer를 함수로 사용하게 되면,

위와 같이, value에서 replacer의 함수를 한번 거치고, 문자열화 된다.

[배열일 때]

배열의 값과 일치하는 값만 문자열화 된다.

예제

var foo = {name: ‘jason’, nickname: ‘ball’, weight: 75};
var useJson = JSON.stringify(foo, [‘nickname’, ‘weight’]);
console.log(useJson); // {“nickname”:”ball”,”weight”:75}

space(선택): 가독성을 목적으로 JSON 문자열 출력에 공백을 삽입하는 데 사용되는데, string이나 number 객체가 될 수 있다.

이 값이 null 이거나, 제공되지 않으면 공백이 사용되지 않는다.

[string일 때]

입력한 string이 공백으로 사용된다.

var space = JSON.stringify({a: 2}, null, ‘string’);
console.log(space);
// {
// string”a”: 2
// }

[number일 때]

입력한 숫자만큼 공백이 생긴다.

var space = JSON.stringify({a: 2}, null, 5);
console.log(space);
// 5칸의 공백
// {
//      “a”: 2
// }

JSON.stringify() 특성

value의 데이터 타입이 number 또는 boolean일 경우, 그 값 자체를 그대로 가져오고, 데이터타입은 string(문자열)이 된다.

JSON.stringify(7) // 7
JSON.stringify(true) // ‘true’
JSON.stringify(null) // null

value의 데이터 타입이 string일 경우, 그 값 자체가 string이 된다.

JSON.stringify(‘foo’) // “‘foo’”

value가 배열일 경우, 그 배열 자체가 string이 된다.

JSON.stringify([1, 'true', true]); // '[1,"true",true]'

value가 배열인데, 배열안에 undefined, 함수, 또는 심볼(symbol)이 있을 경우, 이런 값들은 null로 변환된다.

JSON.stringify([“a”, 7, undefined, function(){return 8}, Symbol(‘’)])
// “[“a”,7,null,null,null]”

value가 객체일 경우, 그 객체 자체가 string이 된다.

이때, 객체의 속성(property)들은 꼭 순서대로 문자열화 되지 않는다.

JSON.stringify({ x: 1, y: 2});
// '{"x":1,"y":2}' 또는 '{"y":2,"x":1}'

value가 객체인데, 객체안에 undefined, 함수, 또는 심볼(symbol)이 있을 경우, 이런 값들은 생략된다.

JSON.stringify({ a: 8, x: undefined, y: function(){return 8}, z: Symbol(‘’) });
// “{“a”:8}”

<구현해야할 문제>

JSON.stringify()와 같은 동작을 하는 함수를 구현하라.

JSON.stringify()에 들어갈 값이 숫자, 문자, boolean, 또는 null 값이 될 수도 있고,

배열, 혹은 객체가 값이 될수도 있다.

단, 값이 함수이거나 undefined일 경우에는 해당이 안된다.


<알고리즘>

  • 우선, 값에 들어갈 타입에 따라 나눈다.
  • 값의 타입이 숫자이거나, boolean 이거나, 값 자체가 null 일 경우, 그 값 자체를 문자열화시킨다.(string의 특성 이용)
  • 값이 원래 문자열일 때, 원래 문자에 있는 “ “ 값 또한 유지시킨다.
  • 값이 배열일 때, 빈 배열일 경우, ‘[ ]’ 을 리턴한다.
  • 빈 배열이 아니라면, for문을 사용해서 배열의 각 element(요소)의 타입을 확인후, 최종 값을 새로운 빈 배열에 추가한다.(재귀함수 사용)
  • 최종 값 양쪽에 배열 괄호를 추가한다.
  • 값이 객체일 때, 빈 객체일 경우, ‘{}’을 리턴한다.
  • 빈 객체가 아니라면, for in문을 사용해서, 객체의 각 value의 타입을 확인후, 최종 값을 새로운 빈 배열에 추가한다.(재귀함수 사용)
  • 최종 값 양쪽에 객체 괄호를 추가한다.
  • 이때, 객체의 value가 함수이거나, undefined일 경우, 그 값을 제거한다.

<구현>

이제, JSON.stringify( )를 직접 구현해보자.

<1> stringifyJSON 함수에 들어갈 값의 타입이 숫자 또는 boolean, 혹은 값 자체가 null일 경우, 그 값을 문자열화 시킨다.

var stringifyJSON = function(obj) {

// typeof null 을 하게되면, object가 된다.
if(typeof obj === ‘number’ || obj === null || typeof obj === ‘boolean’) {
return ‘’ + obj;

어떤 값을 문자열화 시키기 위해서, 어떤 값에 ‘ ’를 더해주면 된다.(string의 특성)

<2> 값의 타입이 문자열일 경우, 원래 문자에 있는 “ ”를 더한다.

} else if(typeof obj === ‘string’) {
return ‘“‘ + obj + ‘“‘;
}

JSON.stringify( )에 문자열 값을 넣을 경우, 문자열 값 자체가 문자열이 되기 때문이다.

var str = “foo”;
JSON.stringify(str) // ““foo””

<3> 값이 배열일때, 배열이 빈 배열일 경우, ‘[ ]’를 리턴한다.

var bin = [];
if(Array.isArray(obj)) {
if(obj.length === 0) {
return ‘[]’;

<4> 배열이 빈 배열이 아닐 경우, for문을 사용해서 배열의 각 element(요소)의 타입을 확인후, 최종 값을 새로운 빈 배열에 추가한다.(재귀함수 사용)

} else {
for(var i = 0; i < obj.length; i++) {
if(typeof obj[i] === ‘string’) {

// obj[i]가 문자열이면, stringifyJSON 함수를 다시 돌려, 원래 문자에 ""를 더한다.
var str = stringifyJSON(obj[i]); 
bin.push(str);
} else if(Array.isArray(obj[i])) {
var arr = stringifyJSON(obj[i]);
bin.push(arr);
} else if(typeof obj[i] === ‘number’) {
bin.push(obj[i]);
} else {
var ifObj = stringifyJSON(obj[i]);
bin.push(ifObj);
}
}
// 최종 값 양쪽에 배열 괄호를 추가한다.
// bin 자체는 배열이기 때문에, 그 배열을 문자열화 시키기 위해,
// 양쪽에 배열 괄호를 더해서, 모양은 배열 모양이지만, 타입은 문자열이 된다.
return ‘[‘ + bin + ‘]’;
}

<5> 값이 객체일때, 객체가 빈 객체일 경우, ‘{}’을 리턴한다.

var createdArr = [];

// object의 length를 확인하기 위해, Object.keys(obj).length를 사용한다.
if(Object.keys(obj).length === 0) {
return ‘{}’;

<6> 객체가 빈 객체가 아닐경우, for in문을 사용해서, 객체의 각 value의 타입을 확인후, 최종 값을 새로운 빈 배열에 추가한다.(재귀함수 사용)

} else {
for(var key in obj) {
if(typeof obj[key] === ‘string’ || typeof obj[key] === ‘boolean’ || obj[key] === null) {
var strKey = stringifyJSON(key);
var strVal = stringifyJSON(obj[key]);

// key와 value(=obj[key])를 재귀함수로 돌려, 문자열화 시킨다.
// 그 후, 원래 객체 안의 내용들(':' 같은 것들)또한 추가시킨다.
var strArr = strKey + ‘:’ + strVal;
createdArr.push(strArr);
} else if(Array.isArray(obj[key])) {
var arrKey = stringifyJSON(key);
var arrVal = stringifyJSON(obj[key]);
var arrArr = arrKey + ‘:’ + arrVal;
createdArr.push(arrArr);

// 객체의 값이 함수이거나, undefined일 경우, 그 객체의 값을 제거한다.
} else if(typeof obj[key] === ‘function’ || obj[key] === undefined) {
delete obj[key];
stringifyJSON(obj);
} else {
var objKey = stringifyJSON(key);
var objVal = stringifyJSON(obj[key]);
var objObj = objKey + ‘:’ + objVal;
createdArr.push(objObj);
}
}
}
// 최종 값 양쪽에 객체 괄호를 추가한다.
// createdArr 자체는 배열이기 때문에, 그 배열을 문자열화 시키고,
// 모양은 객체로 만들어야 하기 때문에,
// 양쪽에 객체 괄호를 더해서, 모양은 객체 모양이지만, 타입은 문자열이 된다.
return ‘{‘ + createdArr + ‘}’;
}
};

<소스코드>

var stringifyJSON = function(obj) { 
// 재귀함수로 사용하기 위해 반복되는 데이터 타입 확인
if(typeof obj === ‘number’ || obj === null || typeof obj === ‘boolean’) {
return ‘’ + obj;
} else if(typeof obj === ‘string’) {
return ‘“‘ + obj + ‘“‘;
}

var bin = [];
if(Array.isArray(obj)) {
if(obj.length === 0) {
return ‘[]’;
} else {
for(var i = 0; i < obj.length; i++) {
if(typeof obj[i] === ‘string’) {
var str = stringifyJSON(obj[i]);
bin.push(str);
} else if(Array.isArray(obj[i])) {
var arr = stringifyJSON(obj[i]);
bin.push(arr);
} else if(typeof obj[i] === ‘number’) {
bin.push(obj[i]);
} else {
var ifObj = stringifyJSON(obj[i]);
bin.push(ifObj);
}
}
return ‘[‘ + bin + ‘]’;
}
} else { 
var createdArr = [];
if(Object.keys(obj).length === 0) { 
return ‘{}’;
} else {
for(var key in obj) {
if(typeof obj[key] === ‘string’ || typeof obj[key] === ‘boolean’ || obj[key] === null) {
var strKey = stringifyJSON(key);
var strVal = stringifyJSON(obj[key]);
var strArr = strKey + ‘:’ + strVal;
createdArr.push(strArr);
} else if(Array.isArray(obj[key])) {
var arrKey = stringifyJSON(key);
var arrVal = stringifyJSON(obj[key]);
var arrArr = arrKey + ‘:’ + arrVal;
createdArr.push(arrArr);
} else if(typeof obj[key] === ‘function’ || obj[key] === undefined) {
delete obj[key];
stringifyJSON(obj);
} else {
var objKey = stringifyJSON(key);
var objVal = stringifyJSON(obj[key]);
var objObj = objKey + ‘:’ + objVal;
createdArr.push(objObj);
}
}
}
return ‘{‘ + createdArr + ‘}’;
}
}; 
// 위 'stringifyJSON' 함수를 아래와 같은 값으로 확인해 볼 수 있다.
// console.log(stringifyJSON([8, “hi”]));
// console.log(stringifyJSON({“a”:[],”c”: {}, “b”: true}));
// console.log(stringifyJSON([{“a”:”b”}, {“c”:”d”}]));
// console.log(stringifyJSON({‘functions’: function(){},’undefined’: undefined}));

위 소스코드는 배열 또는 객체 안에 element들의 데이터 타입을 반복적으로 확인하기 위해 재귀함수를 사용했다.

어떤 데이터 타입이든 문자열화 시키기 위해, 어떤 값에 ‘ ’를 더해줘 문자열화 시켰다.

이를 바탕으로 재귀함수를 활용하여, 반복적으로 데이터 타입을 확인후, 최종적으로 원래 값 자체에 데이터 타입만 문자열화 시킨 값을 리턴할 수 있는, 즉 JSON.stringify()와 같은 함수를 구현했다.