꼭 자바가 아니더라도 웹 서버 개발자로 면접에 가게 되면 꽤 자주 듣는 질문 중 하나가 JSON을 다룰 때 어떤 라이브러리를 사용했는가 였습니다. API를 다루는 백엔드 개발자라면 서버에서 클라이언트로 데이터를 전송하는 경우 JSON을 일반적으로 사용한다고 알고 있습니다. 하지만 저는 당시 지식이 별로 없었던 관계로 @RestController에서 자동으로 JSON으로 변환하여 반환하여 내부적으로 어떤 라이브러리를 사용하는지는 모르겠다고 답하였고 떨어졌던 기억이 있습니다. 늦었지만 지금이라도 JSON에 대해 정리하며 공부해볼까 합니다. 우선 부족하더라도 적어놓고 보완해나갈 예정이니, 내용 중 틀린 부분이나 첨언할 내용이 있다면 댓글 달아주시면 감사하겠습니다.
- JSON이란
- 클라이언트에서의 사용법
- 서버에서의 사용법
- 더 나아가
_____
JSON이란
JSON이란 JavaScript Object Notation의 줄임말이다. 즉, 자바스크립트 객체의 문법[1]으로 구조화된 데이터를 표현하기 위한 문자 기반의 표준 포맷[2]이다. 예를 들면 아래와 같다.
{
"deptCd" : 01,
"empCd" : 01,
"empNm" : "jspark"
}
위처럼 JSON 안에는 JavaScript의 기본 데이터 타입인 문자열, 숫자, 배열, 불리언 및 타 객체를 포함해 데이터 계층을 구축할 수 있다[16]. 참고로 JSON은 문자열과 프로퍼티의 이름 작성 시 쌍따옴표만을 사용해야 한다. 작은 따옴표는 사용 불가하다.
클라이언트에서의 사용법
위에서 JSON에 대해 살펴보았는데, 이는 네트워크를 통해 전송할 때 아주 유용하다[3][4]. 예를 들어 웹 개발자라면 클라이언트/서버 환경에서 개발을 하게 되는데 이때 아래와 같이 JSON 문자열로 변환(Serialization) 후 통신하게 된다.
var empStr = '{"deptCd":01, "empCd":01, "empNm":"jspark"}';
따라서 웹서버로 보내는 혹은 웹서버로부터 받는 데이터는 모두 위와 같은 문자열(string)이기에 데이터를 다루기 위해선 간단한 처리가 필요하다. 아래 웹서버로 데이터를 보내는[6] 코드를 첨부한다.
function sendJsonByPost () {
var xmlhttp = new XMLHttpRequest();
var url = "http://localhost:8080/emp/add2"; // [7]
xmlhttp.open("POST", url, true);
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState === 4 && xmlhttp.status === 200) {
var json = JSON.parse(xmlhttp.responseText);
console.log(json);
}
}
var data = JSON.stringify({"deptCd" : "02", "empCd" : "02", "empNm" : "cylee"});
// JSON.stringify 함수는 아래와 같이 JSON을 문자열화한다.
// var data = '{"deptCd" : "02", "empCd" : "02", "empNm" : "cylee"}';
xmlhttp.send(data);
}
즉 데이터(JSON 문자열)에 억세스하기 위해서는 네이티브 JSON 객체로 변환(Deserialization)될 필요가 있는데, JavaScript는 JSON 전역 객체를 통해 문자열과 JSON 객체의 상호 변환[5]을 지원한다. 코드로 정리해본다.
서버에서의 사용법
앞서 네트워크를 통해 데이터가 넘어올 때 JSON 문자열로 넘어온다고 언급하였다[8][9]. 앞에서 작성하였던 sendJsonByPost 함수에서 넘기는 데이터를 받기 위해 스프링 MVC를 이용해 아래와 같이 작성해보았다(참고: 전체 소스).
@Controller
@RequestMapping(value = "/emp")
public class EmployeeController {
private static final Logger logger = LoggerFactory.getLogger(EmployeeController.class);
private static List<EmployeeDto> empList = new ArrayList<EmployeeDto>();
@RequestMapping(value = "/add1", method = RequestMethod.POST, headers={"content-type=application/json"})
@ResponseBody // [10]
public void insertEmployee1(@RequestBody EmployeeDto employeeDto) {
logger.debug("employeeDto is '{}'", employeeDto);
empList.add(employeeDto);
logger.debug("===== empList start =====");
for(EmployeeDto emp : empList) {
logger.debug("emp is '{}'", emp);
}
logger.debug("===== empList end =====");
}
@RequestMapping(value = "/add2", method = RequestMethod.POST, headers={"content-type=application/json"})
public String insertEmployee2(@RequestBody EmployeeDto employeeDto) {
logger.debug("employeeDto is '{}'", employeeDto);
empList.add(employeeDto);
logger.debug("===== empList start =====");
for(EmployeeDto emp : empList) {
logger.debug("emp is '{}'", emp);
}
logger.debug("===== empList end =====");
return "redirect:/emp/list1";
}
}
코드가 좀 긴데, sendJsonByPost 함수가 보내는 JSON 문자열은 위에서 insertEmployee2 메소드를 타게 된다. 보면 해당 메소드는 @RequestMapping 어노테이션을 통해 다음 설정을 해주었다[11].
- Path(URL): /emp/add2
- HTTP Method: POST
- Content-Type: application/json
또한 POST일 때 클라이언트에서는 데이터(JSON 문자열)를 HTTP 본문에 담아 보내기 때문에 서버에서 @RequestBody 어노테이션[12]을 이용해 데이터를 받아주었다[14].
한편 @RequestBody, @ResponseBody 등의 어노테이션을 스프링에서 지원해주기에 JSON 처리가 수월해졌지만 JsonUtil, ResultUtil 등의 클래스를 생성해 직접 JSON 처리를 할 필요가 있을 수도 있다. 다음 글을 참고하면 좋다.
더 나아가
유튜브 드림코딩 채널에서 JSON에 관한 실용적인 강좌가 있어 소개된 코드를 남긴다.
// 1. Object to JSON
let json = JSON.stringify(true);
console.log(json); // true
json = JSON.stringify(['apple', 'banana']);
console.log(json); // ["apple", "banana"]
const rabbit = {
name: 'tori',
color: 'white',
size: null,
birthDate: new Date(),
// symbol: Symbol("id"),
jump: () => {
console.log(`${name} can jump!`); // name -> this.name
},
};
json = JSON.stringify(rabbit);
console.log(json); // {"name":"tori","color":"white","size":null,"birthDate":"2021-06-18T02:03:22.578Z"}
/*
* jump 함수는 object에 있는 데이터가 아니기 때문에 json에 포함되지 않는다.
* 추가로 Symbol과 같이 자바스크립트에만 있는 특별한 데이터도 json에 포함되지 않는다.
* 추가적으로, stringify 메소드에 배열 혹은 콜백함수를 추가적으로 인자로 주어[17] 섬세하게 다룰 수 있다.
*/
json = JSON.stringify(rabbit, ['name', 'color']);
console.log(json); // {"name":"tori","color":"white"}
json = JSON.stringify(rabbit, (key, value) => {
console.log(`key: ${key}, value: ${value}`);
return value;
});
console.log(json);
/*
* 콘솔 출력 결과
*
* key: , value: [object Object]
* key: name, value: tori
* key: color, value: null
* key: birthDate, value: 2020-05-29T13:29:51.267Z
* key: jump, value: () => {console.log(`$name} can jump!`);}
* {"name":"tori","color":"white","size":null,"birthDate":"2020-05-29T13:29:51.267Z"}
*
* 즉 모든 key와 value가 콜백함수에 전달되나, 가장 처음 전달되는 것은 rabbit 객체를 싸고 있는 최상위다.
*/
json = JSON.stringify(rabbit, (key, value) => {
console.log(`key: ${key}, value: ${value}`);
return key === 'name' ? 'ellie' : value;
});
console.log(json);
/*
* 콘솔 출력 결과
*
* key: , value: [object Object]
* key: name, value: tori
* key: color, value: null
* key: birthDate, value: 2020-05-29T13:29:51.267Z
* key: jump, value: () => {console.log(`$name} can jump!`);}
* {"name":"ellie","color":"white","size":null,"birthDate":"2020-05-29T13:29:51.267Z"}
*/
// 2. JSON to Object
json = JSON.stringify(rabbit);
const obj = JSON.parse(json);
console.log(obj); // {name: "ellie", color: "white", size:null, birthDate: "2020-05-29T13:29:51.267Z"}
// 2-A)
rabbit.jump(); // can jump!
// obj.jump(); // 함수는 직렬화시 미포함
// 2-B)
console.log(rabbit.birthDate.getDate()); // 29
// console.log(obj.birthDate.getDate()); // obj.birthDate는 string Date 객체의 메소드 사용 불가
json = JSON.stringify(rabbit, (key, value) => {
console.log(`key: ${key}, value: ${value}`);
return key === 'birthDate' ? new Date(value) : value;
});
console.log(obj.birthDate.getDate()); // 29
이 외 공부해야 할 부분에 대한 링크를 남겨놓고 추후 정리하고자 한다.
- 우아한형제들 기술 블로그] Spring Rest Docs 적용
- 쿼리 스트링 문자열을 받아서 JSON 문자열을 리턴하는 자바스크립트 함수
- JAVASCRIPT.INFO] JSON과 메서드
_____
1. JSON이 JavaScript 객체 문법과 매우 유사하지만 딱히 JavaScript가 아니더라도 JSON을 읽고 쓸 수 있는 기능이 다수의 프로그래밍 환경에서 제공된다.
2. JSON은 순수히 데이터 포맷으로 오직 프로퍼티만 담을 수 있다. 메서드는 담을 수 없다.
3. JSON의 가장 흔한 용도는 웹서버로/로부터 데이터를 변환하는 데 있다.
- JSON.stringify(): 웹서버로 데이터를 보낼 때, 데이터는 문자열(string)로 변환되어야 한다.
- JSON.parse(): 웹서버로부터 데이터를 받을 때, 데이터는 항상 문자열(string)이다.
4. JSON 외에 요청/응답 데이터의 형식으로 쓰이는 것 중 XML이 있다.
- What is the difference between JSON and XML?
- JSON vs. XML: What's the difference?
- XML vs. JSON: 웹 API를 위한 더 나은 선택
5. 문자열에서 네이티브 객체로 변환하는 것을 파싱(Parsing)이라고 한다. 네트워크를 통해 전달할 수 있게 객체를 문자열로 변환하는 과정은 문자열화(Stringification)이라고 한다.
6. 예에서는 POST 메소드를 사용했지만, GET으로도 서버와 통신하는 것이 가능하다. 다음 글에서 순수 자바스크립트를 이용해 JSON을 서버로 보내고, 받는 코드를 소개한다.
7. URL을 적을 때 프로토콜을 기재하지 않는 등 올바른 형식이 아니라면 CORS 정책 위반 이슈가 발생한다.
8. 위에서 웹서버로 JSON 데이터를 보내는 소스를 소개하였다. 이때 JSON 네이티브 객체가 아니라 JSON 문자열을 서버로 보냈는데, 만약 JSON 객체를 보낸다면 400 에러(클라이언트 오류)가 발생하게 된다.
9. 서버쪽 API 테스트를 위해서는 간편히 Postman을 사용할 수 있다.
10.
11. 자세한 내용은 Spring RequestMapping에 대해 설명한 다음 링크에서 확인할 수 있다.
12. 반면 GET 메소드를 통해 쿼리 스트링 형식[13]으로 데이터가 넘어올 때는 @RequestParam 어노테이션을 통해 데이터를 받을 수 있다.
13. 쿼리 스트링 형식과 JSON 형식은 각각 아래와 같다.
// QUERYSTRING
name=jspark&age=21
// JSON
{
"name" : "jspark",
"age" : 21
}
14. @RequestBody를 통해 JSON 문자열 외에 쿼리 스트링 또한 받을 수 있다. 클라이언트에서 넘겨주는 컨텐트 타입을 application/json에서 application/x-www-form-urlencoded로 바꿔 주면 쿼리 스트링으로 데이터가 넘어오게 된다. 단 이 경우 JSON과 같이 DTO에 매핑하지 못한다는 단점이 있다[15].
15. 다음 글을 참고한다.
16. Date 타입은 JSON 표준이 아니다.
17. stringify() 메소드는 JSON 인터페이스에 정의되어 있다.
_____
참고자료
- MDN Web Docs] JSON으로 작업하기
- 스택오버플로우] Sending a JSON to server and retrieving a JSON in return, without jQuery
- 어제도 오늘도 내일도 언제나 하루] 크롬 크로스 도메인 무시하기
- TOAST Meetup] Postman 개요/설치/사용법/활용 방법
- Baeldung] Jackson JSON Tutorial
- Baeldung] Spring RequestMapping
- HomoEfficio] 요청 Body로 보내지는 JSON의 행방불명
- 스택오버플로우] How are parameters sent in an HTTP POST request?
'공부 > JavaScript' 카테고리의 다른 글
배열 API (0) | 2021.07.19 |
---|---|
Date validation (0) | 2021.04.10 |
번역] 11가지 까다로운 자바스크립트 질문 (0) | 2021.01.23 |
기타 (0) | 2021.01.03 |
var과 let의 차이점 (0) | 2021.01.02 |
댓글