JSON(JavaScript Object Notation)은 객체와 배열을 글 하나로 적는 약속입니다. JSON.stringify는 객체를 JSON 글로 적은 새 문자열을 돌려줍니다. JSON.parse는 JSON 글에서 새 객체를 만들고, 깨진 글이면 SyntaxError로 멈춥니다.
객체는 프로그램이 도는 동안에만 있습니다. 파일에 남기거나 다른 프로그램에 보내려면 글로 바꿔야 하고, 받은 쪽은 그 글을 다시 객체로 되돌립니다. 이 레슨에서는 객체와 글 사이를 오가는 두 함수를 보고, 글에서 되돌린 copy가 무엇을 가리키는지 그림에서 먼저 맞혀 봅니다.
1JSON.stringify: 객체를 글로
그림 코드 1~6번 줄에 찍는 줄 둘을 더했습니다. text는 객체를 가리킬까요, 문자열을 가리킬까요? 무엇을 찍을지 답을 정한 뒤 펼쳐 보세요.
const item = {
name: '커피',
price: 4000,
};
const text =
JSON.stringify(item);
console.log(typeof text);
console.log(text);
실행 결과 보기
string
{"name":"커피","price":4000}예상과 다르면 그곳이 배울 곳입니다. typeof가 string을 찍었으니 text는 객체가 아니라 문자열 하나입니다(수·글자·불리언과 typeof). 그래서 8번 줄은 객체를 찍을 때와 달리 글자를 그대로 찍습니다.
5~6번 줄은 한 문장입니다. 폰 화면에 맞추려고 등호 뒤에서 줄을 나눴습니다.
JSON.stringify(item)은 item이 가리키는 객체의 줄을 읽어, JSON 글로 적은 새 문자열을 돌려줍니다.
이 글은 키와 문자열 값을 큰따옴표로 감싸고, 수는 따옴표 없이, 사이에 빈칸 없이 적습니다. 객체를 찍은 { name: '커피', price: 4000 }과 글 {"name":"커피","price":4000}은 따옴표와 빈칸이 다릅니다.
배열도 같습니다. JSON.stringify(['커피', '라테'])는 글 ["커피","라테"]를 돌려줍니다.
그림에서 5~6번 줄은 객체 ①의 두 줄을 읽어 글 하나를 만들고, 그 글에 이름 text를 붙입니다. ①은 그대로 남습니다.
2JSON.parse: 글에서 새 객체로
그림 코드 전체에 copy를 고치는 줄과 찍는 줄 둘을 더했습니다.
const item = {
name: '커피',
price: 4000,
};
const text =
JSON.stringify(item);
const copy = JSON.parse(text);
copy.price = copy.price + 500;
console.log(copy);
console.log(item);
{ name: '커피', price: 4500 }
{ name: '커피', price: 4000 }7번 줄의 JSON.parse(text)는 글 text를 읽어 새 객체를 만들고, 그 객체에 이름 copy를 붙입니다. 글 속 4000은 따옴표가 없어 다시 수가 됩니다. 그래서 8번 줄의 +는 글자를 이어 붙이지 않고 더해 4500을 만듭니다.
8번 줄은 copy가 가리키는 새 객체의 price 줄만 바꿉니다. item은 처음 객체를 가리키고 있어, 10번 줄이 찍은 price는 4000 그대로입니다(규칙 3).
그림에서 7번 줄은 글을 읽어 새 객체 ②를 만듭니다. ①과 ②는 줄이 같아도 다른 객체입니다.
3JSON 글의 규칙과 깨진 글
JSON 글은 코드의 객체와 닮았지만 쓰는 법이 더 엄격합니다. 같은 객체를 두 꼴로 적어 견주면 이렇습니다.
| 자리 | 코드의 객체 | JSON 글 |
|---|---|---|
| 키 | name · 'name' | "name" |
| 문자열 | '커피' | "커피" |
| 끝 쉼표 | 써도 됨 | 안 됨 |
수와 true · false · null은 두 꼴 모두 따옴표 없이 적습니다.
규칙을 하나라도 어긴 글은 JSON 글이 아니라서 JSON.parse가 읽지 못합니다. 아래 text는 키와 값을 작은따옴표로 감쌌습니다.
const text = "{'name':'커피'}";
console.log('읽기 시작');
const item = JSON.parse(text);
읽기 시작<anonymous_script>:1{'name':'커피'} ^ SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2) at JSON.parse (<anonymous>) at main.js:3:19오류 메시지는 '1번 자리에서 속성 이름이나 }를 기다렸다'는 뜻입니다. 자리(position)는 0부터 세어 1번 자리는 { 다음 글자이고, ^가 그 작은따옴표를 가리킵니다.
오류 메시지 맨 위 줄 <anonymous_script>:1은 main.js의 줄이 아니라 읽던 글의 1번 줄이라는 뜻이고, 그 아래 줄이 읽던 글입니다.
이름은 SyntaxError지만 코드의 문법 오류가 아닙니다. 읽기 시작이 먼저 찍혔고 main.js가 든 at 줄이 3번 줄을 가리키니, 3번 줄을 실행하다가 멈춘 실행 중 오류입니다(오류 메시지 읽기). 코드가 아니라 글이 틀렸습니다.
예제
받은 주문 글로 계산하기
다른 프로그램이 보낸 주문 한 건이 JSON 글로 왔습니다. 글을 객체로 되돌려 금액을 셉니다.
const text =
'{"item":"라테","qty":2}';
const order = JSON.parse(text);
console.log(order.item);
console.log(order.qty * 4500);
라테
90001~2번 줄은 한 문장입니다. JSON 글이 큰따옴표만 쓰니, 글을 담는 자바스크립트 문자열은 바깥을 작은따옴표로 감쌉니다. 3번 줄이 새 객체를 만들어 order.qty는 수 2입니다.
보낸 쪽이 "qty":"2"처럼 수를 큰따옴표로 감쌌다면 order.qty는 문자열 '2'라서, order.qty + 1이 3이 아니라 '21'이 됩니다(글자를 수로: Number와 NaN). 되돌린 값의 종류는 글에 적힌 대로입니다.
과목 끝의 할 일 앱도 할 일 목록을 JSON 글로 바꿔 저장해 두고, 다시 열 때 그 글을 읽어 배열로 되돌립니다.
자주 틀리는 곳
틀린 생각: 객체를 찍은 꼴이 곧 JSON 글이다.
console.log(item)이 찍는 { name: '커피' }도 글처럼 보이니, 그대로 옮겨 적으면 JSON 글이 될 것 같습니다.
const text = "{ name: '커피' }";
const item = JSON.parse(text);
<anonymous_script>:1{ name: '커피' } ^ SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3) at JSON.parse (<anonymous>) at main.js:2:19찍은 꼴은 사람이 읽기 좋게 보인 것이라 키에 따옴표가 없고 문자열은 작은따옴표입니다. JSON.parse는 2번 자리의 키 name에서 큰따옴표를 찾다가 멈춥니다.
맞는 규칙: 찍은 객체 꼴은 JSON 글이 아닙니다. JSON 글은 손으로 옮기지 말고 JSON.stringify로 만듭니다.
틀린 생각: copy와 item은 줄이 같으니 copy === item도 true다.
글로 바꿨다가 되돌린 copy는 item과 줄이 모두 같으니, 둘을 견주면 같다고 나올 것 같습니다.
const item = { name: '커피' };
const text =
JSON.stringify(item);
const copy = JSON.parse(text);
console.log(copy);
console.log(copy === item);
{ name: '커피' }
falsecopy를 찍으면 item과 같은 줄이 보이지만, 4번 줄의 JSON.parse는 새 객체를 만듭니다. === 연산자는 줄을 견주지 않고 두 이름이 같은 객체를 가리키는지 묻습니다(객체를 넘기고 복사하기).
맞는 규칙: JSON.parse는 부를 때마다 새 객체를 만듭니다. 되돌린 값이 맞는지 보려면 객체끼리 === 연산자로 견주지 말고 copy.name === item.name처럼 줄의 값을 견줍니다.
틀린 생각: JSON.stringify는 객체의 속성을 모두 글에 적는다.
객체를 글로 바꾸니, 속성이 몇 개든 빠짐없이 글에 들어갈 것 같습니다.
const item = {
name: '커피',
price: 4000,
hot: undefined,
sale: () => 500,
};
const text =
JSON.stringify(item);
console.log(text);
{"name":"커피","price":4000}JSON 글에는 함수와 undefined를 적는 꼴이 없습니다. 그래서 JSON.stringify는 오류 없이 hot과 sale 두 속성을 빼고 적습니다.
맞는 규칙: 함수와 undefined인 속성은 글에서 말없이 빠집니다. 객체를 복사만 하려면 글을 거치지 말고 structuredClone을 씁니다(객체를 넘기고 복사하기). undefined인 속성도 그대로 남습니다. 다만 함수가 든 객체는 structuredClone도 복사하지 못하고 멈춥니다.
틀린 생각: 읽을 글이 없으면 JSON.parse도 undefined를 돌려준다.
없는 속성을 읽으면 멈추지 않고 undefined가 나오니(객체: 이름 붙은 칸 묶음), 읽을 글이 아직 없을 때도 JSON.parse가 조용히 undefined를 돌려줄 것 같습니다.
const store = {};
const text = store.todos;
const todos = JSON.parse(text);
<anonymous_script>:1undefined^ SyntaxError: "undefined" is not valid JSON at JSON.parse (<anonymous>) at main.js:3:20store에 todos 속성이 없어 2번 줄의 text는 undefined입니다. JSON.parse는 받은 값을 글로 바꿔 읽는데, 글 'undefined'는 JSON 글이 아니라서 멈춥니다.
맞는 규칙: JSON.parse에 넘기기 전에 글이 들어 있는지 봅니다. 오류 메시지가 "undefined" is not valid JSON이면 글이 아니라 글을 꺼내 온 줄을 고칩니다. 12단원에서 브라우저에 저장해 둔 글을 꺼낼 때도 먼저 글이 들어 있는지 봅니다.
확인 문제
1. 주문 한 건을 글로 바꿔 찍습니다. 무엇이 찍힐까요? 한 줄을 글자 그대로 쓰세요.
const order = {
item: '라테',
qty: 2,
memo: undefined,
};
const text =
JSON.stringify(order);
console.log(text);
힌트
JSON 글에서 키와 문자열을 어떤 따옴표로 감싸는지, 글에 적을 수 없는 값이 무엇인지 봅니다.
정답
{"item":"라테","qty":2}키와 문자열 '라테'는 큰따옴표로 감싸고, 수 2는 따옴표 없이 적습니다. memo는 undefined라서 글에서 빠집니다.
흔한 오답은 { item: '라테', qty: 2 }입니다. 객체를 찍은 꼴을 쓴 것이라, text가 객체가 아니라 문자열이라는 것을 놓쳤습니다. memo까지 적은 답은 undefined인 속성도 글에 든다고 본 것이고, "2"처럼 수를 큰따옴표로 감싼 답은 수도 글자로 적힌다고 본 것입니다.
2. 아래 코드는 3번 줄에서 SyntaxError로 멈춥니다. 글에 담긴 메뉴와 수량은 그대로 두고, 2번 줄의 따옴표만 고쳐 1이 찍히게 하세요.
const text =
"{'item':'커피','qty':1}";
const order = JSON.parse(text);
console.log(order.qty);
힌트
JSON 글 안에 쓸 수 있는 따옴표가 무엇인지 정한 뒤, 그 글을 감싸는 바깥 따옴표를 고릅니다.
정답
const text =
'{"item":"커피","qty":1}';JSON 글 안의 키와 문자열은 큰따옴표로 감싸고, 바깥은 작은따옴표로 바꿉니다. 그러면 3번 줄이 새 객체를 만들고 4번 줄이 1을 찍습니다. 바깥을 백틱으로 감싼 `{"item":"커피","qty":1}`도 같은 글을 담아 1이 찍힙니다(템플릿 문자열로 출력 꾸미기).
흔한 오답은 바깥까지 큰따옴표로 둔 "{"item":"커피","qty":1}"입니다. 바깥 문자열이 "{"에서 끝나 버려, 문법 오류(SyntaxError)로 1번 줄도 실행되지 않습니다.
3. 게임 캐릭터의 정보를 글로 저장해 두었다가 다시 불러옵니다. 무엇이 찍힐까요? 보기에서 고르세요.
const player = {
name: '토끼',
level: 3,
};
const saved =
JSON.stringify(player);
player.level = 4;
const copy = JSON.parse(saved);
console.log(copy.level);
- ①
3 - ②
4 - ③
undefined
힌트
5~6번 줄의 글이 언제 만들어지는지, 7번 줄이 바꾸는 것이 객체인지 글인지 봅니다.
정답
3①입니다. 5~6번 줄은 그때 player의 줄을 읽어 글 {"name":"토끼","level":3}을 만듭니다(규칙 2). 7번 줄은 객체의 level 줄만 4로 바꾸고, 이미 만든 글은 그대로입니다. 8번 줄은 그 글에서 새 객체를 만들어 level이 3입니다.
- ② 글 saved가 player 객체를 따라 바뀐다고 봤습니다. saved는 객체에 붙은 이름이 아니라, 5~6번 줄에서 만든 새 문자열을 가리킵니다.
- ③
JSON.parse도 문자열을 돌려준다고 봤습니다. 문자열에는 level 속성이 없어undefined가 됩니다.JSON.parse는 글에서 새 객체를 만듭니다.
컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요.
예제 코드를 main.js에 저장해 돌린 뒤, 2번 줄의 글을 "{'item':'라테','qty':2}"로 바꿉니다. 바깥은 큰따옴표, 안쪽 키와 값은 작은따옴표입니다. 무엇이 찍히고 어디서 멈출지 먼저 적고, 바꿔 돌려서 맞춰 봅니다. 아무것도 찍지 않고 3번 줄에서 SyntaxError로 멈추지 않으면 3절을 다시 봅니다.
저장하고 돌리는 법은 내 컴퓨터에서 돌려 보기에 있습니다.