Section 8. 자바스크립트 Object 깊게 다루기 (JSON)
I. JSON JavaScript Object Notation
이런 형태로 작성된 문서를 XML이라고 한다. XML은 간단히 말해서 복잡할 수 있는 정보를 서버와 클라이언트에서 주고 받기 적합한 형태로 표현한 것이라고 한다. tag를 사용하여 정보와 구조가 명확하게 드러나는 특징이 있다.
하지만 가독성이 조금 떨어질 수 있는데 여기서 강점을 가질 수 있는 것이 바로 JSON이다.
JSON은 보다 간결한 형태로 구조화된 정보를 표기한다.
JSON은 정리를 하자면 다음과 같다
- 복잡한 구조를 가질 수 있는 데이터를 한 줄의 문자열로 표현한 것.
- 서버와 클라이언트 등 데이터들을 주고받는 주체들 사이에 널리 사용된다.
II. JSON 객체의 정적 메서드
1. stringify - 객체를 문자열로 직렬화 serialize 하는 메서드
const person = {
name: '김달순',
age: 23,
languages: ['Korean', 'English', 'French'],
education: {
school: '한국대',
major: ['컴퓨터공학', '전자공학'],
graduated: true,
}
};
const personStr = JSON.stringify(person);
console.log(typeof personStr); //string
console.log(personStr);
//{"name":"김달순","age":23,"languages":["Korean","English","French"],
"education":{"school":"한국대","major":["컴퓨터공학","전자공학"],"graduated":true}}
객체 personStr이 문자열로 직렬화가 되어서 출력이 되었다.
데이터 형태별 직렬화 결과는 다음과 같다
[
JSON.stringify(1), //1
JSON.stringify(Infinity), //null
JSON.stringify(NaN), //null
JSON.stringify('가나다'), //"가나다"
JSON.stringify(true), //true
JSON.stringify(null), //null
JSON.stringify(undefined), //undefined
JSON.stringify([1, 2, 3]), //[1,2,3]
JSON.stringify({x: 1, y: 2}), //{"x":1,"y":2}
JSON.stringify(new Date()), //"2023-04-06T02:02:13.397Z"
]
.forEach(i => console.log(i));
Infinity와 NaN은 직렬화 처리가 되지 않아 null이 된 것을 알 수 있고 Date는 부분적으로만 직렬화가 된 것을 알 수 있다.
값이 함수인 프로퍼티는 직렬화되지 않는다
const obj = {
x: 1,
y: 2,
z: function () { return this.x + this.y }
}
console.log(obj.z()) //3
const objStr = JSON.stringify(obj);
console.log(objStr); //{"x":1,"y":2}
z는 직렬화가 되지 않는다.
2번째 인자 : replacer 함수
직렬화될 방식을 지정하는 함수이다.
const obj = {
a: 1,
b: '2',
c: 3,
d: true,
e: false
}
// 1. key와 value 매개변수
const objStr1 = JSON.stringify(obj, (key, value) => {
if (key && key < 'a' || key > 'c') {
// 해당 프로퍼티 생략
return undefined;
// ⚠️ 조건에 key && 을 붙이지 않으면 항상 undefined가 반환됨
// key가 공백('')일 때(value는 객체 자체) undefined를 반환하므로...
// key와 value를 로그로 출력해보며 확인해 볼 것
}
if (typeof value === 'number') {
return value * 10;
}
return value;
});
console.log(objStr1); //{"a":10,"b":"2","c":30}
key가 a보다 작거나 c보다 크면 undefined를 반환하라고 해서 d와 e는 직렬화가 되지 않는 것을 확인할 수 있다. 즉, undefined를 반환하라는 것은 생략하라는 의미이다.
그리고 값이 숫자인 경우에는 10을 곱해서 반환하라고 해서 값에 10이 곱해져서 직렬화가 된 것을 볼 수 있다.
이렇게 어떤 객체를 직렬화 할때 조건을 넣어 줄 수 있다.
아니면 어떤 프로퍼티만 직렬화가 되게끔 명시를 하는 방법도 있다.
// 2. 반환한 key의 배열 매개변수
const objStr2 = JSON.stringify(obj, ['b', 'c', 'd']);
console.log(objStr2); //{"b":"2","c":3,"d":true}
3번째 인자 : 인덴트
만약에 직렬화된 문자열이 조금 알아보기 쉽게 되었으면 좋겠다~ 라고 한다면 3번째 인자를 통해 인덴트 한다.
const obj = {
a: 1,
b: {
c: 2,
d: {
e: 3
}
}
};
[
JSON.stringify(obj, null), //{"a":1,"b":{"c":2,"d":{"e":3}}}
JSON.stringify(obj, null, 1),
JSON.stringify(obj, null, 2),
JSON.stringify(obj, null, '\t')
]
.forEach(i => console.log(i));
이처럼 3번째 인자에 어떤 조건을 주느냐에 따라서 직렬화의 모양을 바꿀 수 있다.
객체의 toJSON 프로퍼티
const obj = {
x: 1,
y: 2,
toJSON: function () {
return '철권8 언제 나와?';
}
}
console.log(
JSON.stringify(obj)
); //"철권8 언제 나와?"
toJSON은 어떤 객체가 직렬화가 될때 이런 값을 리턴해라~ 라고 정의 해주는 것이다.
어떤 객체에다가 toJSON 프로퍼티를 함수로 넣어주게 되면 직렬화가 될때 그 함수가 실행 된다.
2. parse - 역직렬화
[
JSON.parse('1'),
JSON.parse('"가나다"'), // ⚠️ 안쪽에 따옴표 포함해야 함
JSON.parse('true'),
JSON.parse('null'),
JSON.parse('[1, 2, 3]'),
JSON.parse('{"x": 1, "y": 2}') // ⚠️ key도 따옴표로 감싸야 함
]
.forEach(i => console.log(i));
//1
//가나다
//true
//null
//[1, 2, 3]
//{x: 1, y: 2}
직렬화와 반대로 작동하는 것을 볼 수 있다.
자바스크립트 코드가 문자열로 들어가는 것이기 때문에 코드가 문자열일 경우는 따옴표로 감싸주어야 한다.
2번째 인자 : receiver 함수
const objStr = '{"a":1,"b":"ABC","c":true,"d":[1,2,3]}';
const obj = JSON.parse(objStr, (key, value) => {
if (key === 'c') {
// 해당 프로퍼티 생략
return undefined;
}
if (typeof value === 'number') {
return value * 100;
}
return value;
});
console.log(obj); // ⚠️ 내부까지 적용(배열 확인해 볼 것)
//a: 100, b: 'ABC', d: Array(3)}
내부까지 적용이 된 것을 볼 수 있다.
III. 깊은 복사 deep copy
JSON을 이용하면 깊은 복사가 가능하다.
const obj1 = {
a: 1,
b: {
c: 2,
d: {
e: 3,
f: {
g: 4
}
}
}
}
const obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj1);
console.log(obj2);
obj1.a++;
obj1.b.c++;
obj1.b.d.e++;
obj1.b.d.f.g++;
console.log(obj1);
console.log(obj2);
obj2에 obj1을 직렬화 한것에 역직렬화를 한것을 할당했고 obj1의 프로퍼티를 1씩 증가시킨다음 둘다 출력을 해보면 다음과 같다.
obj2는 obj1의 영향을 받지 않은 것을 볼 수 있다.
이유는 obj2는 obj1과 같은 값을 참조하는 게 아니라 문자열을 받아서 그걸 해석해서 값이 나온(역직렬화) 완전 새로운 것을 obj2에 할당 했기 때문이다. 완전히 다른 객체가 할당이 된 것.
함수, Date, Symbol, BigInt 프로퍼티는 JSON 방식으로는 불가 또는 제한적이라는 사실을 알고 넘어가자.
structuredClone이라는 것이 있다. 특징은 다음과 같다
- 아직은 일부 브라우저 및 환경에서만 지원한다.
- JSON 방식보다 빠르고 효율적인 깊은 복사가 된다.
- Date와 BigInt를 제대로 복사한다.
일단은 이런게 있다는 사실만 알고 넘어가자.