네트워크를 통해 객체를 어딘가에 보내거나 로깅 목적으로 객체를 출력해야 한다면 객체를 문자열로 전환해야 한다.
let user = {
name: "John",
age: 30,
toString() {
return `{name: "${this.name}", age: ${this.age}}`;
}
};
alert(user); // {name: "John", age: 30}
개발 과정에서 프로퍼티가 추가되거나 삭제, 수정되면 toString을 매번 수정해야하기 때문에 문제가 생긴다.
자바스크립트에는 이런 문제를 해결할 수 있는 방법이 있다.
JSON.stringfy
JSON(JavaScript Object Notation)은 값이나 객체를 나타내주는 범용 포맷으로,RFC 4627 표준에 정의되어 있다.
자바스크립트가 제공하는 JSON 관련 메서드
- JSON.stringify : 객체를 JSON으로 바꿔준다.
- JSON.parse : JSON을 객체로 바꿔준다.
JSON.stringify 예)
let student = {
name: 'John',
age: 30,
isAdmin: false,
courses: ['html', 'css', 'js'],
wife: null
};
let json = JSON.stringify(student);
alert(typeof json); // 문자열이네요!
alert(json);
/* JSON으로 인코딩된 객체:
{
"name": "John",
"age": 30,
"isAdmin": false,
"courses": ["html", "css", "js"],
"wife": null
}
*/
이렇게 변경된 문자열은 JSON으로 인코딩된(JSON-encoded), 직렬화 처리된(serialized), 문자열로 변환된(stringified),
결집된(marshalled) 객체라고 부른다.
객체는 문자열로 변환된 후에야 네트워크를 통해 전송하거나 저장소에 저장할 수 있다.
JSON으로 인코딩된 객체는 일반 객체와 다른 특징이 있다.
- 문자열은 큰따옴표로 감싸야 한다. JSON에서는 작은 따옴표나 백틱을 사용할 수 없다.
- 객체 프로퍼티 이름은 큰따옴표로 감싸야한다.(age:30, "age:30")
JSON.stringify는 객체뿐만 아니라 원시값에도 적용할 수 있다.
적용할 수 있는 자료형
- 객체 {...}
- 배열 [...]
- 원시형:
- 문자형
- 숫자형
- 불린형 갑 true와 false
- null
// 숫자를 JSON으로 인코딩하면 숫자입니다.
alert( JSON.stringify(1) ) // 1
// 문자열을 JSON으로 인코딩하면 문자열입니다(다만, 큰따옴표가 추가됩니다).
alert( JSON.stringify('test') ) // "test"
alert( JSON.stringify(true) ); // true
alert( JSON.stringify([1, 2, 3]) ); // [1,2,3]
JSON은 데이터 교환을 목적으로 만들어진 언어에 종속되지 않는 포맷이다
따라서 자바스크립트 특유의 객체 프로퍼티는 JSON.stringify가 처리할 수 없다.
호출 무시되는 프로퍼티
- 함수 프로퍼티(메서드)
- 심볼형 프로퍼티(키가 심볼인 프로퍼티)
- 값이 undefined인 프로퍼티
let user = {
sayHi() { // 무시
alert("Hello");
},
[Symbol("id")]: 123, // 무시
something: undefined // 무시
};
alert( JSON.stringify(user) ); // {} (빈 객체가 출력됨)
JSON.stringify의 장점중 하나는 중첩 객체도 알아서 문자열로 바꿔준다.
let meetup = {
title: "Conference",
room: {
number: 23,
participants: ["john", "ann"]
}
};
alert( JSON.stringify(meetup) );
/* 객체 전체가 문자열로 변환되었습니다.
{
"title":"Conference",
"room":{"number":23,"participants":["john","ann"]},
}
*/
JSON.stringify사용할 때 순환 참조가 있으면 원하는 대로 객체를 문자열로 바꾸는 게 불가능하다.
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: ["john", "ann"]
};
meetup.place = room; // meetup은 room을 참조합니다.
room.occupiedBy = meetup; // room은 meetup을 참조합니다.
JSON.stringify(meetup); // Error: Converting circular structure to JSON
room.occupiedBy는 meetup을, meetup.place는 room을 참조하기 때문에 JSON으로의 변환이 실패
room.occupiedBy는 meetup을, meetup.place는 room을 참조하기 때문에 JSON으로의 변환이 실패했습니다.
replacer로 원하는 프로퍼티만 직렬화 하기
JSON.strignify 전체 문법
let json = JSON.stringify(value[, replacer, space])
value : 인코딩 하려는 값
replacer : JSON으로 인코딩 하길 원하는 프로퍼티가 담긴 배열. 또는 매핑 함수 function(key,value)
space : 서식 변경 목적으로 사용할 공백 문자 수
# 대다수의 경우 JSON.stringify에는 인수 하나만 넘겨서 사용한다 하지만 순환 참조를 다뤄야하는 경우같이
전환 프로세스를 정교하게 조정하려면 두 번째 인수를 사용해야한다
JSON으로 변환하길 원하는 프로퍼티가 잠긴 배열을 두번째 인수로 넘겨주면 이 프로퍼티들만 인코딩 할 수 있다.
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup은 room을 참조합니다.
};
room.occupiedBy = meetup; // room references meetup
alert( JSON.stringify(meetup, ['title', 'participants']) );
// {"title":"Conference","participants":[{},{}]}
-> 배열에 name을 넣지 않아서 출력도니 문자열의 participants가 비어버린 문제가 발생
순환 참조를 발생시키는 room.occupiedBy만 제외하고 모든 프로퍼티를 배열에 넣기
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup references room
};
room.occupiedBy = meetup; // room references meetup
alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
/*
{
"title":"Conference",
"participants":[{"name":"John"},{"name":"Alice"}],
"place":{"number":23}
}
*/
occupiedBy를 제외한 모든 프로퍼티가 직렬화(하지만 배열이 길다는 느낌이 들 수 있다)
replacer에 전달되는 함수(replacer 함수)는 프로퍼티 (키,값) 쌍 전체를 대상으로 호출되는데
반드시 기존 프로퍼티 값을 대신하여 사용할 값을 반환해야 한다.
특정 프로퍼티를 직렬화에서 누락시키려면 반환 값을 undefined로 만들면 된다.
아래예시는 occupiedBy를 제외한 모든 프로퍼티의 값을 변경없이 그대로 직렬화 하고 있다.
occupiedBy는 undefined를 반환하게 해 직렬화에 포함하지 않는다.
let room = {
number: 23
};
let meetup = {
title: "Conference",
participants: [{name: "John"}, {name: "Alice"}],
place: room // meetup은 room을 참조합니다
};
room.occupiedBy = meetup; // room은 meetup을 참조합니다
alert( JSON.stringify(meetup, function replacer(key, value) {
alert(`${key}: ${value}`);
return (key == 'occupiedBy') ? undefined : value;
}));
/* replacer 함수에서 처리하는 키:값 쌍 목록
: [object Object]
title: Conference
participants: [object Object],[object Object]
0: [object Object]
name: John
1: [object Object]
name: Alice
place: [object Object]
number: 23
*/
replacer 함수는 재귀적으로 키-값 쌍을 처리하는데, 함수 내에서 this는 현재 처리하고 있는 프로퍼티가 위치한 객체를 가리킨다.
첫 얼럿 창에는 ":[object Object]" 가 뜨는데 이것은 함수가 최초로 호출될 때 {"":meetup}형태의 "래퍼 객체"가 만들어지기 때문.
replacer함수가 가장 처음으로 처리해야하는 (key,value) 쌍에서 키는 빈문자열 값은 변환하고자하는 객체(meetup)전체가 되는것
space로 가독성 높이기
JSON.stringify(value,replacer,space)의 세번째 인수 space는 가독성을 높이기 위해 중간에 삽입해줄 공백 문자 수를 나타낸다.
space는 가독성을 높이기 위한 용도로 만들어졌기 때문에 단순 전달 목적이라면 space 없이 직렬화하는 편이다.
(로깅,이나 가독성을 높이는 목적)
space에 2를 넘겨주면 자바스크립트는 중첩 객체를 별도의 줄에 출력해주고 공백 문자 두 개를 써 들여쓰기 한다.
let user = {
name: "John",
age: 25,
roles: {
isAdmin: false,
isEditor: true
}
};
alert(JSON.stringify(user, null, 2));
/* 공백 문자 두 개를 사용하여 들여쓰기함:
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
/* JSON.stringify(user, null, 4)라면 아래와 같이 좀 더 들여써집니다.
{
"name": "John",
"age": 25,
"roles": {
"isAdmin": false,
"isEditor": true
}
}
*/
커스텀 "toJSON"
toString을 사용해 객체를 문자형으로 변환시키는 것 처럼 JSON.stringify는 이런 경우를 감지하고 toJSON을 자동으로 호출해준다.
let room = {
number: 23
};
let meetup = {
title: "Conference",
date: new Date(Date.UTC(2017, 0, 1)),
room
};
alert( JSON.stringify(meetup) );
/*
{
"title":"Conference",
"date":"2017-01-01T00:00:00.000Z", // (1)
"room": {"number":23} // (2)
}
*/
Date 객체의 내장 메서드 toJSON이 호출되면서 date의 값이 문자열로 변환
커스텀 메서드toJSON 추가 예시
let room = {
number: 23,
toJSON() {
return this.number;
}
};
let meetup = {
title: "Conference",
room
};
alert( JSON.stringify(room) ); // 23
alert( JSON.stringify(meetup) );
/*
{
"title":"Conference",
"room": 23
}
*/
toJSON은 JSON.stringify(room)을 직접 호출할 때도 사용할 수 있고, room과 같은 중첩객체에도 구현하여 사용가능
JSON.parse
JSON.parse를 사용하면 JSON으로 인코딩된 객체를 다시 객체로 디코딩 할 수 있다.
let value = JSON.parse(str, [reviver]);
str : JSON 형식의 문자열
reviver : 모든 (key,value) 쌍을 대상으로 호출되는 function(key,value) 형태의 함수로 값을 변경시킬 수 있다.
예)
// 문자열로 변환된 배열
let numbers = "[0, 1, 2, 3]";
numbers = JSON.parse(numbers);
alert( numbers[1] ); // 1
JSON.parse는 중첩 객체에도 사용할 수 있다.
let userData = '{ "name": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }';
let user = JSON.parse(userData);
alert( user.friends[1] ); // 1
중첩 객체나 중첩 배열이 있느면 JSON도 복잡해지는데 결국엔 JSON 포맷을 지켜야한다.
JSON을 만들떄 흔히 저지르는 실수
let json = `{
name: "John", // 실수 1: 프로퍼티 이름을 큰따옴표로 감싸지 않았습니다.
"surname": 'Smith', // 실수 2: 프로퍼티 값은 큰따옴표로 감싸야 하는데, 작은따옴표로 감쌌습니다.
'isAdmin': false // 실수 3: 프로퍼티 키는 큰따옴표로 감싸야 하는데, 작은따옴표로 감쌌습니다.
"birthday": new Date(2000, 2, 3), // 실수 4: "new"를 사용할 수 없습니다. 순수한 값(bare value)만 사용할 수 있습니다.
"friends": [0,1,2,3] // 이 프로퍼티는 괜찮습니다.
}`;
JSON은 주석을 지원하지 않는다.
JSON 포맷이 까다로운 규칙을 가진 이유는
쉽고 빠르며 신뢰할 수 있을만한 파싱 알고리즘을 구현하기 위해서이다.
reviver
서버로부터 문자열로 변환된 meetup 객체를 전송 받았다고 가정
// title: (meetup 제목), date: (meetup 일시)
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
이 문자열을 역 직렬화(JSON.parse)
let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
let meetup = JSON.parse(str);
alert( meetup.date.getDate() ); // 에러!
meetup.date의 값은 Date 객체가 아니라 문자열이라 에러가 발생한다.
이때 문자열을 Date로 전환해야 한다는 것을 알리기 위해 reviver을 사용한다.
(모든 값은 그대로 , date는 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() );
중첩 객체에도 적용 가능하다.
let schedule = `{
"meetups": [
{"title":"Conference","date":"2017-11-30T12:00:00.000Z"},
{"title":"Birthday","date":"2017-04-18T12:00:00.000Z"}
]
}`;
schedule = JSON.parse(schedule, function(key, value) {
if (key == 'date') return new Date(value);
return value;
});
alert( schedule.meetups[1].date.getDate() );
'자바 스크립트' 카테고리의 다른 글
자바스크립트 나머지 매개변수와 전개 문법 (0) | 2020.12.16 |
---|---|
자바스크립트 iterable (0) | 2020.12.14 |
자바스크립트 배열 (0) | 2020.12.13 |
자바스크립트 문자열 (0) | 2020.12.12 |
자바스크립트 숫자형 (0) | 2020.12.11 |
댓글