on your mark

[JAVASCRIPT.INFO] 5.12 JSON과 메서드 본문

WEB/Javascript

[JAVASCRIPT.INFO] 5.12 JSON과 메서드

dev-olive 2023. 1. 17. 20:37

5.12 JSON과 메서드

JSON.stringify

JSON은 본래 자바스크립트에서 사용할 목적으로 만들어진 포맷이다. 그런데 라이브러리를 사용하면 자바스크립트가 아닌 언어에서도 JSON을 충분히 다룰 수 있어, JSON을 데이터 교환 목적으로 사용하는 경우가 많다.

  • JSON.stringify - 객체를 JSON으로 바꿔준다.
  • JSON.parse - JSON을 객체로 바꿔준다.

JSON.stringify는 객체뿐만 아니라 원시값에도 적용 가능하다.

JSON은 데이터 교환을 목적으로 만들어진 언어에 종속되지 않는 포맷이다. 따라서 자바스크립트 특유의 객체 프로퍼티는 JSON.stringify가 처리할 수 없다.

JSON.stringify 호출 시 무시되는 프로퍼티는 다음과 같다.

  • 함수 프로퍼티(메서드)

  • 심볼형 프로퍼티(키가 심볼인 프로퍼티)

  • 값이 undefined인 프로퍼티

    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

replacer로 원하는 프로퍼티만 직렬화하기

let json = JSON.stringify(value [, replacer, space]);
  • value : 인코딩하려는 값
  • replacer : JSON으로 인코딩하길 원하는 프로퍼티가 담긴 배열 또는 매핑 함수 (function(key, value))
  • space : 서식 변경 목적으로 사용할 공백 문자 수

순환 참조를 다뤄야 하는 경우 같이 전환 프로세스를 정교하게 조정하려면 두 번째 인수를 사용해야 한다.

let room = {
  number: 23
};
let meetup = {
  title: "Conference",
  participants: [{name: "John"}, {name: "Alice"}],
  place: room
}
room.occupiedBy = meetup;        // room references meetup
alert(JSON.stringify(meetup, ['title', 'participants']));
// {"title":"Conference","participants":[{},{}]}

배열에 넣어준 프로퍼티가 잘 출력되는 것을 확인할 수 있다. 그런데 배열에 name 을 넣지 않아서 출력된 문자열의 participants가 비어버렸다.

순환 참조를 발생시키는 프로퍼티 room.occupiedBy만 제외하고 모든 프로퍼티를 배열에 넣어보자.

alert(JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']));
/*
{
  "title":"Conference",
  "participants":[{"name":"John"},{"name":"Alice"}],
  "place":{"number":23}
}
*/

occupiedBy를 제외한 모든 프로퍼티가 직렬화되었다.

replacer자리에 배열 replacer 함수를 전달해 길어진 배열을 처리할 수 있다.

replacer에 전달되는 함수는 프로퍼티 (key, value) 쌍 전체를 대상으로 호출되는데, 반드시 기존 프로퍼티 값을 대신하여 사용할 값을 반환해야 한다. 특정 프로퍼티를 직렬화에서 누락시키려면 반환 값을 undefined로 만들면 된다.

JSON.stringify(meetup, function replace(key, value) {
  alert(`${key}: ${value}`)
  return (key === "occupiedBy") ? undefined : value;
})

replace함수가 중첩 객체와 배열의 요소까지 포함한 모든 키-값 쌍을 처리하고 있다. replacer함수는 재귀적으로 키-값 쌍을 처리하는데, 함수 내에서 this는 현재 처리하고 있는 프로퍼티가 위치한 객체를 가리킨다.

첫 얼럿창에 예상치 못한 문자열(":[object Object]")이 뜨는 걸 볼 수 있는데, 이는 함수가 최초로 호출 때 {"" : meetup} 형태의 "래퍼 객체"가 만들어지기 때문이다. replace함수가 가장 처음으로 처리해야 하는 (key, value) 쌍에서 키는 빈 문자열, 값은 변환하고자 하는 객체(meetup) 전체가 된다.

이렇게 replacer 함수를 사용하면 중첩 객체 등을 포함한 객체 전체에서 원하는 프로퍼티만 선택해 직렬화할 수 있다.

space로 가독성 높이기

space는 가독성을 높이기 위한 용도로 만들어졌기 때문에 단순 전달 목적이라면 space없이 직렬화해도 된다.

let user = {
  name: "John",
  age: 25,
  roles: {
    isAdmin: false,
    isEditor: true
  }
};

JSON.stringify(user, null, 2);
/* 공백 문자 두 개를 사용하여 들여쓰기함:
{
  "name": "John",
  "age": 25,
  "roles": {
    "isAdmin": false,
    "isEditor": true
  }
}
*/

커스텀 "toJSON"

toString을 사용해 객체를 문자형으로 변환시키는 것처럼, 객체에 toJSON이라는 메서드가 구현되어 있으면 객체를 JSON으로 바꿀 수 있다.

let room = {
  number: 23
}
let meetup = {
  title: "Conference",
  date: new Date(Date.UTC(2017, 0, 1)),
  room
};
JSON.stringify(meetup);
/*
  {
    "title":"Conference",
    "date":"2017-01-01T00:00:00.000Z",  // (1)
    "room": {"number":23}               // (2)
  }
*/

Date 객체의 내장 메서드 toJSON이 호출되면서 date의 값이 문자열로 변환된다.

let room = {
  number: 23,
  toJSON() {
    return this.number;
  }
};
let meetup = {
  title: "Conference",
  room
};
JSON.stringify(room)        // 23
JSON.stringify(meetup);
/*
    {
        "title": "Conference",
        "room": 23
    }
*/

JSON.parse

JSON으로 인코딩된 객체를 다시 객체로 디코딩할 수 있다.

let value = JSON.parse(str, [reviver]);
  • str - JSON형식의 문자열
  • reviver - 모든 (key, value) 쌍을 대상으로 호출되는 function(key, value) 형태의 함수로 값을 변경시킬 수 있음
let userData = '{"name": "John", "age": 35, "isAdmin": false, "friend": [0,1,2,3]}';
let user = JSON.parse(userData);

reviver 사용하기

let str = '{"title":"Conference","date":"2017-11-30T12:00:00.000Z"}';
let meetup = JSON.parse(str);
meetup.date.getDate();            // Error

meetup.date의 값은 Date 객체가 아니고 문자열이기 때문에 에러가 발생한다.

모든 값은 그대로 하지만 date만큼은 Date객체를 반환하도록 함수를 구현하면,

let meetup = JSON.parse(str, function(key, value) {
  if(key === "date")    return new Date(value);
  return value;
});
meetup.date.getDate();        // 정상적으로 작동