그렇다고 해서 JSON.stringify가 모든 자바스크립트 자료형을 처리하는 있는 것은 아니다. 자바스크립트 특유의 객체 프로퍼티는 JSON.stringify가 처리하지 않는다. 함수(무시됨)와 심볼(무시됨), 값이 undefined인 프로퍼티(무시되거나 null로 변환), 혹은 순환참조(에러발생)는 stringify로 처리 시 제외되는 것을 확인할 수 있다. (다른 곳에 이걸 보내서 어디에 쓸건데..?)
JSON.stringify는 원시값에도 적용할 수 있다.
stringify에 적용 가능한 자료형
객체
{ ... } : 키-값 쌍으로 구성된 데이터
배열
[ ... ] : 순서가 있는 데이터
원시형
* 문자형(String) : 텍스트 데이터 * 숫자형(Number) : 정수와 실수 * 논리형(Boolean) : true 혹은 false * null : 빈 값을 나타내는 특수 값
stringify에 적용 불가능한 프로퍼티
함수
함수 프로퍼티(메서드)
심볼형
키(key)가 심볼(Symbol)인 프로퍼티
원시형
undefined
아래의 예시를 통해, 우리는 한 가지 사실을 더 알 수 있다. 바로, stringify는 중첩 객체도 알아서 문자열로 바꿔준다는 것이다.
const test = {
object : {name : "kim"}, // {"name":"kim"}
string : 'string', // "string"
number : 28, // 28
array: [1, 2, 3], // [1,2,3]
boolean1: true, // true
boolean2: false, // false
empty1 : null, // null
empty2 : undefined, // 출력 X
[Symbol("id")]: 123, // 출력 X
function : function Fnc(){
console.log("hi");
}, // 출력 X
}
let json = JSON.stringify(test);
console.log(json);
// 출력 : {"object":{"name":"kim"},"string":"string","number":28,"array":[1,2,3],"boolean1":true,"boolean2":false,"empty1":null}
2) replacer
대다수의 경우 value 인수 하나만을 넘겨서 사용하지만,
순환 참조와 같이 전환 프로세스를 정교하게 다뤄야 할 경우 두 번째 인수인 replacer를 사용한다.
(순환참조의 경우 어쩌구저쩌구 생각해야 하는 게 있지만, 순환 참조를 만드는 것 자체를 권장하지 않기도 하고,,, 필요할 때 찾아보자)
replacer의 경우 인코딩 하길 원하는 프로퍼티가 담긴 배열 혹은 키(key)와 값(value)이 담겨있는 매핑 함수를 통해 원하는 값들만 인코딩하도록 만들 수 있다.
replacer에 인코딩하기 원하는 프로퍼티를 배열로 넘겼을 경우
const obj = {
name : "lee",
height : 164,
weight : 50
}
let replacer1 = JSON.stringify(obj, ["name", "height"]);
console.log(replacer1); // 출력 : {"name":"lee","height":164}
인코딩하기 원하는 객체와 프로퍼티 명이 담긴 배열을 넘겼을 경우, 객체의 키와 배열 안의 값이 동일할 경우에만 인코딩 된다.
배열 안의 값이 객체 안에 존재하지 않거나, 객체의 키가 배열에 존재하지 않는 경우는 무시된다.
replacer에 매핑함수를 넘겼을 경우
const obj = {
name : "lee",
height : 164,
weight : 50
}
function replacer(key, value){
if(!key) return value;
if(typeof value === 'number'){
return value;
}
}
let replacer2 = JSON.stringify(obj, replacer);
console.log(replacer2); // 출력 : {"height":164,"weight":50}
매핑함수는 인수로 인코딩할 객체와 프로퍼티가 들어가기 때문에 key, value가 반드시 매개변수로 존재해야 한다. 이후 매핑 함수 내부에서 조건식과 같은 로직을 거친 후 return 되는 값이 함수, 심볼, undefined가 아닌 값들을 모아서 인코딩한다.
매핑함수에서 재밌는 점은 첫 번째 호출값으로 객체 전체가 넘어온다는 것이다. 첫 번째 호출에서 전체 객체에 대해 실행된 후, 이후 각 프로퍼티의 키와 값을 순차적으로 검사하며 실행된다.
const obj = {
name : "lee",
height : 164,
weight : 50
}
function replacer(key, value){
if(typeof value === 'number'){
return value;
}
}
let replacer2 = JSON.stringify(obj, replacer);
console.log(replacer2); // 출력 : undefined
위에 replacer에서 if(!key) return value라는 코드 한 줄을 삭제했더니 undefined가 출력되었다. 중단점을 찍어보니 맨 처음 인수로 객체 전체가 넘어왔는데 객체의 타입이 object 이기 때문에 조건식에 부합하지 않아 undefined를 반환했기 때문이다. 이 첫 번째 호출에서 undefined를 반환하면 JSON.stringify는 객체 변환을 중단하고 최종 결과로 undefined를 반환한다.
조건식의 내용만 undefined를 하고 싶다면 다음과 같이 쓸 수 있다. 아래 코드에선 value가 number인 경우 undefined 시키고 나머지는 return value 하도록 작성했다. 여기서 조건식이 object여서 전체 객체를 건드릴 경우, 혹은 다른 모든 코드들을 return value 시켜주지 않는 경우에도 함수의 특성상 undefined를 반환하기 때문에 무시당할 것이다.
const obj = {
name : "lee",
height : 164,
weight : 50
}
function replacer(key, value){
if(typeof value === 'number'){
return undefined;
}
return value;
}
let replacer2 = JSON.stringify(obj, replacer);
console.log(replacer2); // 출력 : {"name":"lee"}
3) space
세 번째 인수 space는 가독성을 높이기 위해 중간에 삽입해 줄 공백 문자 수를 나타낸다.
지금까진 space 없이 메서드를 호출했기 때문에 인코딩 된 JSON에 들여 쓰기나 여분의 공백문자가 존재하지 않았다.
이 경우 코드가 빽빽이 나열되어 있기 때문에 가독성이 떨어질 수 있다.
space는 이러한 가독성을 높이기 위한 용도로 만들어졌기 때문에, 단순히 전달할 목적이라면 잘 사용하지 않는다.
space에 4를 입력했을 경우 각각의 프로퍼티 앞에 공백 4칸이 생성된다.
const obj = {
name : "kim",
age : 28
}
console.log(JSON.stringify(obj, null, 4))
/* 출력 :
{
"name": "kim",
"age": 28
}
*/
커스텀 메서드 toJSON()
toJSON() 는 객체 안에서 사용되는 메서드로 stringify와 같이 인코딩하
객체에 정의되어 있고, 그 객체를 JSON.stringify() 메서드로 객체를 인코딩할 경우 toJSON()이 자동으로 실행된다.
기본형
const number = {
first : 1,
toJSON(){
return this.first;
}
}
console.log(JSON.stringify(number)) // 출력 : 1
replacer 처럼 조건식을 걸 수도 있다.
const obj = {
name : "kim",
age : 28,
toJSON(){
let newObj = "";
for (let key in this){
if(typeof this[key] == 'number' && key !== 'toJSON') {
newObj += this[key];
}
}
return newObj;
}
}
console.log(JSON.stringify(obj)) // 출력 : "28"
내장 객체 Date 같은 경우 toJSON 메서드가 내장되어 있다.
let date = {
date: new Date(Date.UTC(2017, 0, 1)),
};
console.log(JSON.stringify(date))
/*
{
"date":"2017-01-01T00:00:00.000Z", // Date객체에 toJSON메서드가 내장되어 있기 때문에, 자동으로 문자열로 변환됨
}
*/
중첩 객체에 toJSON()이 있는 경우 toJSON() 메서드를 거친 다음 반환값을 출력한다.
let number = {
first: 1,
toJSON() {
return this.first;
}
};
let test = {
title: "숫자",
number
};
console.log( JSON.stringify(test) );
/*
{
"title":"숫자",
"number": 1
}
*/
JSON.parse()
JSON.parse()를 사용하면 JSON으로 인코딩 된 객체를 다시 객체로 디코딩할 수 있다.
기본형
JSON.parse(string, [reviver]);
매개변수
JSON.parse()는 매개변수로 string과 reviver를 받는다.
string : JSON 형식의 문자열
reviver : 디코딩 되는 문자열안의 키-값을 변형할 매핑함수 function(key, value)가 들어간다.
1) String
첫번째 인수로 JSON 형식의 문자열이 들어간다.
parse()를 통해 문자열을 디코딩하면 자바스크립트 자료형으로 변환된다.
JSON 문법으로 인코딩했던 객체를 parse() 하니 문자열이 아닌 기존의 객체 배열이 출력된다.
const users = {
name : 'kim',
age : 28
}
let json = JSON.stringify(users);
console.log(json) // 출력 : {"name":"kim","age":28}
console.log(JSON.parse(json)) // 출력 : { name: 'kim', age: 28 }
중첩 객체나, 중첩 배열에서도 동일하게 parse할 경우 기존의 배열로 돌아가는 것을 확인할 수 있다.
만약, 디버깅 등의 목적으로 직접 JSON을 만든다면 다음 사항을 주의하면 좋다.
문자형태의 프로퍼티 명과 값은 큰따옴표로 감싸야한다.
순수한 값(bare value)만 사용할 수 있다. ( 예 : new Date()와 같은 형태는 사용 X)
JSON은 주석을 지원하지 않는다. (추가시 유효하지 않은 형식이 됨)
2) reviver
두번째 매개변수 reviver에는 함수를 인수로 받는다.
이 함수는 디코딩되는 문자열 안의 key와 value 값을 받아 변환시킬 수 있다.
예를 들어 내장 객체 Date 가 문자열로 변환된 후, parse를 통해 디코딩되면 Date로 돌아오는 것이 아닌 문자열 그대로 유지된다. 이때, reviver 안에 다시 Date 객체로 변환시키는 함수를 넣으면 변환되는 것을 확인 할 수 있다.
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
let meetup = JSON.parse(str, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( meetup.date.getDate() );