Spring Boot에서의 역직렬화(Deserialize)

..·2025년 2월 19일

finder

목록 보기
18/23

Spring Boot의 CustomDeserializer를 작성하여 문제를 해결하는 과정에서 역직렬화란 무엇인가 그리고 어떻게 작동하는가에 대해 알아보게 되었다.

직렬화(Serialize)

직렬화란 컴퓨터의 데이터 객체를 저장 매체에 저장할 수 있는 형식, 네트워크를 통해 전송할 수 있는 것으로 변환하는 것을 말한다.

e.g., Java 객체를 JSON 데이터 형태로 변환하는 것


역직렬화(Deserialize)

역직렬화란 직렬화의 반대 개념으로, 디스크에 저장된 데이터를 읽거나 네트워크 통신으로부터 받은 데이터를 다시 객체로 변환하는 것을 말한다.

e.g., JSON 데이터를 Java 객체 형태로 변환하는 것

직렬화와 역직렬화의 예시를 이미지로 표현하면 다음과 같다.

왜 이런 과정을 통해 데이터를 Java Object로 변환해야 하는지, 어떤 차이와 문제가 있는지 알아보았다.



객체로 변환하는 과정이 필요한 이유

데이터의 형식에 따른 차이

API 통신을 통해 주고받는 JSON 형식의 데이터는 실제로 텍스트(String) 데이터이므로 바로 Java 객체로 사용할 수 없다.

  • 요청 파라미터(JSON)
{
  	"name": "..",
  	"velog": "@27cean"
}
  • 실제 데이터(String)
"{\"name\": \"..\", \"velog\": \"@27cean\"}"

따라서 이러한 문자열 데이터를 Spring Boot의 애플리케이션에서 사용할 수 있도록 Java 객체로 변환하는 역직렬화 과정이 필요하다.
일반적으로는 @RequestParam@RequestBody를 통해 역직렬화를 수행할 수 있다.


Deserializer 구현이 필요한 이유

@RequestBody는 기본적으로 역직렬화를 수행하지만, 데이터 구조가 복잡하거나 특정한 변환이 필요한 경우에는 기본 Jackson 매핑으로 해결할 수 없다.
Deserializer는 불규칙적인 JSON 데이터를 사용할 경우 또는 사용하려는 데이터가 다양한 형식을 가질 때 구현이 필요하다.

나는 요청 파라미터로 사용하는 JSON 데이터의 수가 불규칙적이라는 점을 고려해서 Deserializer를 직접 구현하게 되었다.



Deserializer 구현하기

1. RequestBody의 DTO 작성하기

// TagDTO.java

@Getter
@Setter
@NoArgsConstructor
public class TagDTO {
    private String name;
    private String type;
    private String comment;

    @ConstructorProperties({"name", "type", "comment"})
    public TagDTO(String name, String type, String comment) {
        this.name = name;
        this.type = type;
        this.comment = comment;
    }
}

@RequestBody로 데이터를 전달할 때, 요청 파라미터 값을 인식하지 못하는 오류를 발견했다.
@ConstructorProperties를 추가하여 요청 파라미터에서 사용한 명칭을 생성자에서 인식할 수 있도록 했다.

2. RequestBody로 DTO 사용하기

    // Controller.java
    
    ... 
    
    @PostMapping("/tag/create")
    public ResponseEntity<String> postTagCreate(@RequestBody List<TagDTO> request) {
        
        ...
        
        return ResponseEntity.ok("태그 생성 완료");
    }

새로운 태그를 생성하기 위해 POST 요청을 보낼 때, List<TagDTO> 형태로 데이터를 전달한다.
1개 또는 여러 개의 태그를 동시에 생성할 수 있게 하기 위하여 DTO 배열의 형태를 사용했다.

3. Deserializer 작성하기

// TagCreateDeserializer.java

public class TagCreateDeserializer extends JsonDeserializer<List<TagDTO>> {
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public List<TagDTO> deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
        JsonNode node = parser.getCodec().readTree(parser);
        List<TagDTO> tagList = new ArrayList<>();

        if (node.isArray()) {
            for (JsonNode jsonNode : node) {
                tagList.add(objectMapper.treeToValue(jsonNode, TagDTO.class));
            }
        } else {
            tagList.add(objectMapper.treeToValue(node, TagDTO.class));
        }
        return tags;
    }
}

List<TagDTO> 형식의 데이터가 들어오면 deserialize의 과정을 통해 역직렬화 한다.


4. 사용하기

API 테스트는 로컬 환경에서 Postman을 이용했다.
Postman에서 POST 요청을 보낼 때, Body에서 raw-JSON을 이용하면 알아서 인식하지만 실제로 요청할 때는 HeadersContent-Type으로 application/json을 사용해야 한다.

  • 하나의 데이터를 보내는 방법

  • 2개 이상의 데이터를 보내는 방법



Deserializer가 동작하는 원리

원래는 직접 작성한 Deserializer를 DTO 필드에 적용할 수 있다.
하지만 이 과정에서 오류가 발생했고, 이를 해결하면서 Deserializer가 동작하는 원리에 대해 알아보게 되었다.

오류 발생 위치

// TagDTO.java

@Getter
@Setter
@NoArgsConstructor

/ * 오류 발생 위치 * /
@JsonDeserializer(using = TagCreateDeserializer.class)
public class TagDTO {
    private String name;
    private String type;
    private String comment;

    @ConstructorProperties({"name", "type", "comment"})
    public TagDTO(String name, String type, String comment) {
        this.name = name;
        this.type = type;
        this.comment = comment;
    }
}

TagDTO에 @JsonDeserializer(using = TagCreateDeserializer.class)를 적용했다.
하지만 애플리케이션을 실행하면 오류가 발생했고, 해당 내용을 삭제한 후에 애플리케이션을 실행하면 Deserializer가 정상 작동하는 것을 확인할 수 있었다.

오류 발생 원인

Jackson과 Deserializer가 동작하는 원리 차이

Jackson이란 JSON 데이터 구조를 처리하는 라이브러리로, 필드 타입을 기반으로 역직렬화 한다.
예를 들어, List<TagDTO> 타입의 필드는 JSON 데이터가 배열이면 리스트로 변환하고, 단일 객체면 리스트에 감싸 변환하는 기본 동작을 수행한다.

하지만 명시적으로 Deserializer에 대해 선언하면 Jackson이 기본 역직렬화 방식 대신 선언한 Deserializer만을 사용하려고 하기 때문에 충돌할 수 있다.

또한 List<TagDTO> 타입을 처리하는 Deserializer를 따로 만들었기 때문에, DTO에 적용할 Deserializer를 명시적으로 선언하면 Jackson이 내부적으로 해당 타입을 인식하지 못할 수 있다.

역직렬화를 수행하는 원리

  1. HTTP 요청이 들어오면 @RequestBody에서 Jackson을 이용해서 JSON 데이터를 Java 객체로 변환한다.

  2. Jackson이 DTO의 필드 타입을 분석하여 자동으로 매핑한다.

  3. 필요한 경우에는 CustomDeserializer를 사용해서 특정 타입의 역직렬화를 진행한다.


위의 과정을 성공적으로 거치면 역직렬화된 객체를 컨트롤러에서 사용할 수 있다.
Spring Boot에서는 ObjectMapper가 역직렬화 과정에서 등록된 Deserializer를 찾아서 자동으로 적용한다. 따라서 @JsonDeserializer를 명시하지 않아도 내가 작성한 Deserializer가 동작할 수 있었다.



마무리

데이터 파이프라인을 구축하고, 데이터를 서비스 형태로 제공하기 위해서 스프링 부트를 이용하고 있는데, 서비스를 구현하는 매 순간이 공부의 연속이다. 모르는 게 너무 많고, 겉보기에 알 수 없는 구성과 흐름이 많다.

이전에 백엔드를 구현한 경험 덕분에 API를 설계하고 작성하는 개념은 알고 있지만, 스프링 부트를 이용하여 내가 원하는 방향대로 요령껏 구현하기 위해서는 앞으로도 공부할 것이 많다고 다시 한번 느꼈다. 그래도 기본적인 틀에 새로운 것을 더하며 이해하고 지식을 쌓을 수 있다는 점에서 재미있는 것 같다.

0개의 댓글