복잡한 개체가 있고 이를 문자열로 변환하거나, 네트워크를 통해 보내거나, 로깅 목적으로 출력하고 싶다고 가정해 보겠습니다.
당연히 이러한 문자열에는 모든 중요한 속성이 포함되어야 합니다.
다음과 같이 변환을 구현할 수 있습니다.
사용자 = { 이름: "존", 나이: 30, toString() { return `{이름: "${this.name}", 나이: ${this.age}}`; } }; 경고(사용자); // {이름: "John", 나이: 30}
…그러나 개발 과정에서 새로운 속성이 추가되고, 이전 속성의 이름이 바뀌고 제거됩니다. 매번 toString
업데이트하는 것은 고통스러울 수 있습니다. 그 안에 있는 속성에 대해 루프를 시도할 수 있지만, 개체가 복잡하고 속성에 중첩된 개체가 있으면 어떻게 될까요? 우리는 그들의 변환도 구현해야 합니다.
다행히도 이 모든 것을 처리하기 위해 코드를 작성할 필요는 없습니다. 작업은 이미 해결되었습니다.
JSON(JavaScript Object Notation)은 값과 객체를 나타내는 일반적인 형식입니다. RFC 4627 표준에 설명되어 있습니다. 처음에는 JavaScript용으로 만들어졌지만 다른 많은 언어에도 이를 처리할 수 있는 라이브러리가 있습니다. 따라서 클라이언트가 JavaScript를 사용하고 서버가 Ruby/PHP/Java/Whatever로 작성된 경우 데이터 교환에 JSON을 사용하는 것이 쉽습니다.
JavaScript는 다음과 같은 메소드를 제공합니다.
JSON.stringify
객체를 JSON으로 변환합니다.
JSON.parse
사용하여 JSON을 다시 객체로 변환합니다.
예를 들어, 여기에서는 학생을 JSON.stringify
.
학생 = { 이름: '존', 나이: 30, isAdmin: 거짓, 강좌: ['html', 'css', 'js'], 배우자: null }; json = JSON.stringify(학생); 경고(json 유형); // 문자열이 있습니다! 경고(json); /* JSON으로 인코딩된 객체: { "이름": "존", "나이": 30, "isAdmin": 거짓, "강좌": ["html", "css", "js"], "배우자": null } */
JSON.stringify(student)
메소드는 객체를 가져와 문자열로 변환합니다.
결과 json
문자열을 JSON으로 인코딩 되거나 직렬화 되거나 문자열화 되거나 마샬링된 객체라고 합니다. 이제 이를 유선으로 보내거나 일반 데이터 저장소에 넣을 준비가 되었습니다.
JSON으로 인코딩된 개체에는 개체 리터럴과 몇 가지 중요한 차이점이 있습니다.
문자열은 큰따옴표를 사용합니다. JSON에는 작은따옴표나 역따옴표가 없습니다. 따라서 'John'
"John"
이 됩니다.
객체 속성 이름도 큰따옴표로 묶여 있습니다. 그것은 의무적입니다. 따라서 age:30
"age":30
이 됩니다.
JSON.stringify
기본 요소에도 적용될 수 있습니다.
JSON은 다음 데이터 유형을 지원합니다.
객체 { ... }
배열 [ ... ]
기본 요소:
문자열,
숫자,
부울 값 true/false
,
null
.
예를 들어:
// JSON의 숫자는 단지 숫자일 뿐입니다. 경고( JSON.stringify(1) ) // 1 // JSON의 문자열은 여전히 문자열이지만 큰따옴표로 묶여 있습니다. 경고( JSON.stringify('test') ) // "테스트" 경고( JSON.stringify(true) ); // 진실 경고( JSON.stringify([1, 2, 3]) ); // [1,2,3]
JSON은 데이터 전용 언어 독립적 사양이므로 일부 JavaScript 관련 개체 속성은 JSON.stringify
에서 건너뜁니다.
즉:
함수 속성(메서드).
기호 키와 값.
undefined
속성을 저장하는 속성입니다.
사용자 = { sayHi() { // 무시됨 Alert("안녕하세요"); }, [Symbol("id")]: 123, // 무시됨 뭔가: 정의되지 않음 // 무시됨 }; 경고( JSON.stringify(user) ); // {} (빈 객체)
보통은 괜찮습니다. 이것이 우리가 원하는 것이 아니라면 곧 프로세스를 사용자 정의하는 방법을 살펴보겠습니다.
가장 좋은 점은 중첩된 개체가 자동으로 지원되고 변환된다는 것입니다.
예를 들어:
만나자 = { 제목: "컨퍼런스", 방: { 번호: 23, 참가자: ["john", "ann"] } }; 경고( JSON.stringify(meetup) ); /* 전체 구조는 문자열화되어 있습니다: { "title": "컨퍼런스", "방":{"번호":23,"참가자":["john","ann"]}, } */
중요한 제한 사항: 순환 참조가 없어야 합니다.
예를 들어:
방 = { 번호: 23 }; 만나자 = { 제목: "컨퍼런스", 참가자: ["john", "ann"] }; Meetup.place = 방; // 모임 참고실 room.occupedBy = 모임; // 룸 참조 모임 JSON.stringify(meetup); // 오류: 원형 구조를 JSON으로 변환하는 중
여기서는 순환 참조로 인해 변환이 실패합니다. room.occupiedBy
meetup
참조하고 meetup.place
room
참조합니다.
JSON.stringify
의 전체 구문은 다음과 같습니다.
let json = JSON.stringify(value[, replacement, space])
값
인코딩할 값입니다.
대체자
인코딩할 속성 배열 또는 매핑 함수 function(key, value)
.
공간
포맷에 사용할 공간의 양
대부분의 경우 JSON.stringify
첫 번째 인수에만 사용됩니다. 그러나 순환 참조를 필터링하는 등 교체 프로세스를 미세 조정해야 하는 경우 JSON.stringify
의 두 번째 인수를 사용할 수 있습니다.
속성 배열을 전달하면 해당 속성만 인코딩됩니다.
예를 들어:
방을 놔두다 = { 번호: 23 }; 만나자 = { 제목: "컨퍼런스", 참가자: [{이름: "John"}, {이름: "Alice"}], 장소: 방 // 모임 참고실 }; room.occupedBy = 모임; // 룸 참조 모임 경고( JSON.stringify(meetup, ['title', 'participants']) ); // {"제목":"컨퍼런스","참가자":[{},{}]}
여기서 우리는 아마도 너무 엄격할 것입니다. 속성 목록은 전체 개체 구조에 적용됩니다. name
목록에 없기 때문에 participants
의 개체는 비어 있습니다.
순환 참조를 발생시키는 room.occupiedBy
를 제외한 모든 속성을 목록에 포함시켜 보겠습니다.
방 = { 번호: 23 }; 만나자 = { 제목: "컨퍼런스", 참가자: [{이름: "John"}, {이름: "Alice"}], 장소: 방 // 모임 참고실 }; room.occupedBy = 모임; // 룸 참조 모임 Alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) ); /* { "title": "컨퍼런스", "참가자":[{"name":"John"},{"name":"Alice"}], "장소":{"번호":23} } */
이제 occupiedBy
제외한 모든 항목이 직렬화됩니다. 하지만 속성 목록이 꽤 깁니다.
다행스럽게도 배열 대신 함수를 replacer
자로 사용할 수 있습니다.
이 함수는 모든 (key, value)
쌍에 대해 호출되며 원래 값 대신 사용될 "교체된" 값을 반환해야 합니다. 또는 값을 건너뛰는 경우 undefined
.
우리의 경우, occupiedBy
제외한 모든 것에 대해 "있는 그대로" value
반환할 수 있습니다. occupiedBy
무시하기 위해 아래 코드는 undefined
반환합니다.
방을 놔두다 = { 번호: 23 }; 만나자 = { 제목: "컨퍼런스", 참가자: [{이름: "John"}, {이름: "Alice"}], 장소: 방 // 모임 참고실 }; room.occupedBy = 모임; // 룸 참조 모임 경고( JSON.stringify(meetup, 함수 대체자(키, 값)) { Alert(`${key}: ${value}`); 반환(키 == '점유된By') ? 정의되지 않음: 값; })); /* 대체자에 제공되는 키:값 쌍: : [객체 객체] 제목: 컨퍼런스 참가자: [객체 객체],[객체 객체] 0: [객체 객체] 이름: 존 1: [객체 객체] 이름: 앨리스 장소: [객체 객체] 번호: 23 점유된 By: [객체 객체] */
replacer
함수는 중첩된 객체와 배열 항목을 포함한 모든 키/값 쌍을 가져옵니다. 재귀적으로 적용됩니다. this
내부 replacer
값은 현재 속성을 포함하는 개체입니다.
첫 번째 통화는 특별합니다. 이는 특별한 "래퍼 개체"인 {"": meetup}
을 사용하여 만들어집니다. 즉, 첫 번째 (key, value)
쌍에는 빈 키가 있고 그 값은 전체적으로 대상 개체입니다. 이것이 위의 예에서 첫 번째 줄이 ":[object Object]"
인 이유입니다.
replacer
에게 가능한 한 많은 전력을 제공하는 것이 아이디어입니다. 필요한 경우 전체 개체까지 분석하고 교체/건너뛸 수 있는 기회가 있습니다.
JSON.stringify(value, replacer, space)
의 세 번째 인수는 예쁜 형식 지정에 사용할 공백 수입니다.
이전에는 모든 문자열화된 개체에 들여쓰기나 추가 공백이 없었습니다. 네트워크를 통해 객체를 보내고 싶다면 괜찮습니다. space
인수는 좋은 출력을 위해서만 사용됩니다.
여기서 space = 2
JavaScript에 객체 내부에 2개의 공백을 들여쓰기하여 여러 줄에 중첩된 객체를 표시하도록 지시합니다.
사용자 = { 이름: "존", 나이: 25세, 역할: { isAdmin: 거짓, isEditor: 사실 } }; Alert(JSON.stringify(user, null, 2)); /* 두 칸 들여쓰기: { "이름": "존", "나이": 25, "역할": { "isAdmin": 거짓, "isEditor": 참 } } */ /* JSON.stringify(user, null, 4)의 경우 결과가 더 들여쓰기됩니다. { "이름": "존", "나이": 25, "역할": { "isAdmin": 거짓, "isEditor": 참 } } */
세 번째 인수는 문자열일 수도 있습니다. 이 경우 들여쓰기에는 공백 대신 문자열이 사용됩니다.
space
매개변수는 로깅 및 좋은 출력 목적으로만 사용됩니다.
문자열 변환을 위한 toString
과 마찬가지로 객체는 to-JSON 변환을 위한 toJSON
메서드를 제공할 수 있습니다. JSON.stringify
가능한 경우 자동으로 호출합니다.
예를 들어:
방 = { 번호: 23 }; 만나자 = { 제목: "컨퍼런스", 날짜: 새로운 날짜(Date.UTC(2017, 0, 1)), 방 }; 경고( JSON.stringify(meetup) ); /* { "title": "컨퍼런스", "날짜":"2017-01-01T00:00:00.000Z", // (1) "방": {"번호":23} // (2) } */
여기서 date
(1)
이 문자열이 된 것을 볼 수 있습니다. 모든 날짜에는 이러한 종류의 문자열을 반환하는 toJSON
메서드가 내장되어 있기 때문입니다.
이제 객체 room
(2)
에 대한 사용자 정의 toJSON
에 추가해 보겠습니다.
방 = { 번호: 23, toJSON() { this.number를 반환합니다. } }; 만나자 = { 제목: "컨퍼런스", 방 }; 경고( JSON.stringify(room) ); // 23 경고( JSON.stringify(meetup) ); /* { "title": "컨퍼런스", "방": 23 } */
보시다시피 toJSON
JSON.stringify(room)
직접 호출하는 경우와 room
다른 인코딩된 개체에 중첩된 경우 모두에 사용됩니다.
JSON 문자열을 디코딩하려면 JSON.parse라는 또 다른 메서드가 필요합니다.
구문:
let value = JSON.parse(str[,viver]);
str
구문 분석할 JSON 문자열입니다.
자극성 음료
각 (key, value)
쌍에 대해 호출되고 값을 변환할 수 있는 선택적 함수(키, 값)입니다.
예를 들어:
// 문자열화된 배열 숫자 = "[0, 1, 2, 3]"; 숫자 = JSON.parse(숫자); 경고(숫자[1]); // 1
또는 중첩된 객체의 경우:
let userData = '{ "이름": "John", "age": 35, "isAdmin": false, "friends": [0,1,2,3] }'; 사용자 = JSON.parse(userData)를 보자; 경고( user.friends[1] ); // 1
JSON은 필요한 만큼 복잡할 수 있으며 개체와 배열에는 다른 개체와 배열이 포함될 수 있습니다. 하지만 동일한 JSON 형식을 따라야 합니다.
다음은 손으로 작성한 JSON의 일반적인 실수입니다(때로는 디버깅 목적으로 작성해야 합니다).
json = `{ name: "John", // 실수: 따옴표가 없는 속성 이름 "surname": 'Smith', // 실수: 값에 작은따옴표(큰따옴표여야 함) 'isAdmin': false // 실수: 키에 작은따옴표(이중따옴표여야 함) "생일": new Date(2000, 2, 3), // 실수: "new"는 허용되지 않으며 순수 값만 허용됩니다. "friends": [0,1,2,3] // 여기서는 모두 괜찮습니다. }`;
게다가 JSON은 주석을 지원하지 않습니다. JSON에 주석을 추가하면 유효하지 않게 됩니다.
따옴표가 없는 키, 주석 등을 허용하는 JSON5라는 또 다른 형식이 있습니다. 그러나 이는 언어 사양이 아닌 독립 실행형 라이브러리입니다.
일반 JSON은 개발자가 게으르기 때문이 아니라 구문 분석 알고리즘을 쉽고 안정적이며 매우 빠르게 구현할 수 있도록 엄격합니다.
서버에서 문자열화된 meetup
개체를 받았다고 상상해 보세요.
다음과 같습니다:
// 제목: (모임 제목), 날짜: (모임 날짜) let str = '{"제목":"회의","날짜":"2017-11-30T12:00:00.000Z"}';
…이제 역 직렬화하여 JavaScript 객체로 다시 전환해야 합니다.
JSON.parse
호출하여 수행해 보겠습니다.
let str = '{"제목":"회의","날짜":"2017-11-30T12:00:00.000Z"}'; Meetup = JSON.parse(str); 경고( Meetup.date.getDate() ); // 오류!
앗! 오류입니다!
meetup.date
의 값은 Date
객체가 아닌 문자열입니다. JSON.parse
해당 문자열을 Date
로 변환해야 한다는 것을 어떻게 알 수 있습니까?
두 번째 인수로 부활 함수를 JSON.parse
에 전달해 보겠습니다. 이 함수는 모든 값을 "있는 그대로" 반환하지만 date
Date
됩니다.
let str = '{"제목":"회의","날짜":"2017-11-30T12:00:00.000Z"}'; Meetup = JSON.parse(str, function(key, value) { if (key == 'date') return new Date(value); 반환값; }); 경고( Meetup.date.getDate() ); // 이제 작동합니다!
그런데 이는 중첩된 객체에도 적용됩니다.
일정을 보자 = `{ "모임": [ {"제목":"회의","날짜":"2017-11-30T12:00:00.000Z"}, {"제목":"생일","날짜":"2017-04-18T12:00:00.000Z"} ] }`; 일정 = JSON.parse(일정, 함수(키, 값) { if (key == 'date') return new Date(value); 반환값; }); 경고(schedule.meetups[1].date.getDate() ); // 작동합니다!
JSON은 대부분의 프로그래밍 언어에 대한 자체 독립 표준과 라이브러리를 갖춘 데이터 형식입니다.
JSON은 일반 객체, 배열, 문자열, 숫자, 부울 및 null
지원합니다.
JavaScript는 JSON으로 직렬화하는 JSON.stringify 메소드와 JSON에서 읽을 수 있는 JSON.parse 메소드를 제공합니다.
두 방법 모두 스마트 읽기/쓰기를 위한 변환기 기능을 지원합니다.
객체에 toJSON
있으면 JSON.stringify
에 의해 호출됩니다.
중요도: 5
user
JSON으로 변환한 다음 이를 다른 변수로 다시 읽어옵니다.
사용자 = { 이름: "존 스미스", 나이: 35 };
사용자 = { 이름: "존 스미스", 나이: 35 }; user2 = JSON.parse(JSON.stringify(user));
중요도: 5
간단한 순환 참조의 경우 이름을 기준으로 직렬화에서 문제가 되는 속성을 제외할 수 있습니다.
그러나 이름은 순환 참조와 일반 속성 모두에 사용될 수 있으므로 이름만 사용할 수 없는 경우도 있습니다. 그래서 우리는 그 가치로 속성을 확인할 수 있습니다.
모든 것을 문자열화 replacer
함수를 작성하되 meetup
참조하는 속성은 제거하세요.
방 = { 번호: 23 }; 만나자 = { 제목: "컨퍼런스", 점유된By: [{이름: "John"}, {이름: "Alice"}], 장소: 방 }; // 순환 참조 room.occupedBy = 모임; Meetup.self = 모임; 경고( JSON.stringify(meetup, 함수 대체자(키, 값)) { /* 귀하의 코드 */ })); /* 결과는 다음과 같아야 합니다: { "title": "컨퍼런스", "점유된By":[{"name":"John"},{"name":"Alice"}], "장소":{"번호":23} } */
방 = { 번호: 23 }; 만나자 = { 제목: "컨퍼런스", 점유된By: [{이름: "John"}, {이름: "Alice"}], 장소: 방 }; room.occupedBy = 모임; Meetup.self = 모임; 경고( JSON.stringify(meetup, 함수 대체자(키, 값)) { return (key != "" && value == 모임) ? 정의되지 않음: 값; })); /* { "title": "컨퍼런스", "점유된By":[{"name":"John"},{"name":"Alice"}], "장소":{"번호":23} } */
여기서는 value
이 meetup
인 것이 일반적인 첫 번째 호출을 제외하기 위해 key==""
테스트도 필요합니다.