오늘
현대 백엔드 개발에서 JSON은 선택이 아닌 필수다. API 통신, 데이터 저장, 설정 파일까지 JSON이 없는 백엔드 시스템은 상상하기 어렵다고 한다. 프론트엔드와 백엔드가 협업할 때 일종의 규약이나 인터페이스 역할을 수행하기도 한다. JSON 포맷을 간단하게 알아보고, 우리가 사용할 Spring에서는 어떻게 사용하는지 알아보자.
JSON(JavaScript Object Notation) 은 사람이 읽고 쓰기 쉬우며, 기계가 파싱하고 생성하기도 쉬운 경량 데이터 교환 포맷이다. 이름에 "JavaScript"가 들어가지만, 언어에 독립적이며 Java, Python, Go, Kotlin 등 사실상 모든 백엔드 언어에서 완벽하게 지원된다.
JSON은 RFC 8259 표준으로 정의되어 있으며(인터넷 기술 표준으로 UTF-8 인코딩을 필수화하고 경량 텍스트 기반의 구조화된 데이터 직렬화 방식을 정의하여 상호 운용성을 강화한 표준안이라고 합니다), 현재 REST API의 사실상 표준 데이터 포맷으로 자리잡고 있다.
JSON은 두 가지 핵심 구조로 이루어진다. 객체 (Object) 키-값 쌍의 집합으로, 중괄호 {}로 표현한다.
{
"id": 1,
"name": "Kim Minsu",
"email": "minsu@example.com",
"isActive": true,
"age": null
}또 배열 대괄호[]를 사용해서 배열처럼 사용할 수도 있다.
{
"users": [
{
"id": 1,
"name": "Kim Minsu"
}, ```
{
"id": 2,
"name": "Lee Jiyeon" }
]
} 백엔드 개발 관점에서 JSON을 이해할 때 필요한 용어가 있다. 바로 직렬화(Serialization)와 역직렬화(Deserialization) 이다. 직렬화란 메모리상에 존재하는 복잡한 Java 객체(Object)를 네트워크로 전송하거나 파일로 저장할때, 이를 연속된 문자열이나 바이트 형태로 변환하는 것을 이야기한다. 반대로 외부에서 들어온 JSON 문자열을 다시 애플리케이션이 다룰 수 있는 객체 상태로 복원하는 과정을 역직렬화라고한다. 이 변환 작업을 통해 우리는 Spring 앱에서 JSON 앱을 다룰 수 있다.
Java 백엔드에서 JSON을 다룰 때 가장 많이 사용되는 라이브러리는 Jackson과 Gson이다. Spring Boot는 기본적으로 Jackson을 내장하고 있어, 실무에서는 Jackson이 표준처럼 쓰인다고 한다(실제로 Spring 공식 문서에서도 Jackson 3를 우선하는 기본 라이브러리로 표현한다).
REST API를 개발할 때 @RestController 어노테이션을 사용하면, 메서드가 반환하는 객체는 HTTP 응답 페이로드(Payload)에 담길 때 알아서 JSON으로 직렬화된다. 반대로 클라이언트(프론트엔드)가 보낸 JSON 형태의 요청 데이터는 @RequestBody를 통해 백엔드 내부의 Java 객체로 깔끔하게 역직렬화되어 사용할 수 있다.
사실 위의 내용을 조사하면서 그렇다면 JSON이 편리한 이유는 자바 코드 내의 객체를 빠르게 저장할 수 있는 경량화된 파일이며, 반대로 JSON 입력 시 손실 없이 모두 객체로 변환할 수 있기 때문이라고 생각했다. 또 프론트엔드와 협업시 JSON은 언어에 의존하지 않기 때문에 프론트엔드와 인터페이스(주고 받을, API 명세)에 유리하지 않을까 생각한다. 따라서 이런 입출력 관점에서 조금 더 조사했다.
위에서는 네트워크 관점에서 협업 관점에서 살펴보았다면, 서버의 초기 설정 데이터를 로드하거나 로그 성격의 데이터를 파일로 백업 해야하는 파일의 영속성이 필요할 때 JSON이 편리할 것이라 생각한다. 파일 단위의 입출력(I/O)에서 JSON은 매우 유용한 저장 인터페이스로 기능한다.
Spring 환경에서는 ObjectMapper 객체를 활용해 I/O 작업을 간단히 구현할 수 있다. 예를 들어, 특정 Java 객체의 상태를 로컬의 .json 파일로 그대로 출력(Write)하여 저장하거나, 반대로 설정이 담긴 JSON 파일을 읽어와(Read) 객체에 매핑할 수 있다.
// User.java
public class User {
private String name;
private int age;
public User() {} // 역직렬화 시 필수
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}(Jackson이 빈 객체를 생성하고 내용을 채워넣는 식이라 default 생성자가 꼭 필요하다고 한다)
// user.json
[
{ "name": "Alice", "age": 29 },
{ "name": "Bob", "age": 34 },
{ "name": "Charlie", "age": 22 },
{ "name": "Diana", "age": 27 },
{ "name": "Edward", "age": 31 }
]다음과 같은 User 클래스와 user의 정보가 담긴 json 파일이 있다고 생각해보자. 먼저 파일을 읽으려면readValue(File, Class)메서드를 사용한다.
import com.fasterxml.jackson.databind.ObjectMapper; // JSON 메서드
import java.io.File; //파일 경로 객체, 파일 지정
import java.util.List; //List type
List<User> users = objectMapper.readValue(
new File("user.json"),
new TypeReference<List<User>>() {} // List 형태의 JSON이므로
);위의 코드 실행 후 아래와 같이 반복문으로 출력해보면 어떻게 저장되는지 확인할 수 있다.
for (User user : users) {
System.out.println(user.getName() + " / " + user.getAge());
}// 터미널
Alice / 29
Bob / 34
Charlie / 22
Diana / 27
Edward / 31다음은 반대로 객체를 JSON 형태로 저장해보자. 이번에는 writeValue(File, Object) 메서드를 사용한다.
List<User> users = List.of(
new User("Alice", 29),
new User("Bob", 34),
new User("Charlie", 22),
new User("Diana", 27),
new User("Edward", 31)
);
objectMapper.writerWithDefaultPrettyPrinter()
.writeValue(new File("user.json"), users);이 때 writerWithDefaultPrettyPrinter()를 사용하면 들여쓰기가 된 형태로 JSON을 저장한다.
// user.json
[ {
"name" : "Alice",
"age" : 29
}, {
"name" : "Bob",
"age" : 34
},
...
]만약 사용하지 않으면 한 줄짜리 포맷으로 저장된다고 한다.
오늘은 JSON에 대해 간단하게 알아보고, Spring이 어떻게 쓰는지 알아보았다. 사실 내가 찾은 예제 코드만 가지고 실제 Spring에서 JSON을 활용한 입출력을 잘 할 수 있을 것 같지는 않다. 어노테이션을 쓰는 방법도 있다고 하는데, JSON 역직렬화 후 파싱 방법이라던가 이전에 배웠던 Spring 자동 DI에서 관리할 때 어떻게 써야 할 지도 생각해보아야 할 것 같다.
참고자료: https://docs.spring.io/spring-boot/reference/features/json.html
댓글 0