티스토리 뷰

SpringBoot

JsonSerializer, JsonDeserializer

파미페럿 2024. 2. 4. 23:03

개발을 하다보면 json 데이터를 주고 받을 때 똑같은 가공 과정을 거쳐야 하는 경우가 있다.

함수로 가공 과정을 정의해 매번 함수를 호출해줘도 되지만 spring에서는 그러한 경우에 쓰라고 @JsonSerialize, @JsonDeserialize 어노테이션을 제공해주고 있다.

 

@JsonSerialize @JsonDeserialize

딱 어노테이션 네이밍에서 볼 수 있듯이 Json 데이터를 주고 받을 때 Serializer, Deserializer에 대해 정의할 수 있는 어노테이션이다.

@JsonSerialize, @JsonDeserialize는 함수, 필드, 파라미터 등에 붙여서 쓸 수 있다.

간단하게 아래와 같이 사용한다.

@JsonSerialize(using = TestSerializer::class)

@JsonDeserialize(using = TestDeserializer::class)

 

 

using말고 다른 설정 값들도 있는데 using으로 충분히 원하는 기능을 해서 사용해보지는 않았다.

찾아본 설정 값들은 아래와 같다.

 

using: Class<? extends JsonSerializer> / Class<? extends JsonDeserializer>

값을 직렬화/역직렬화 하기 위한 serializer, deserializer의 클래스를 설정한다.

밑에 나오는 contentUsing, keyUsing을 설정하지 않아도 using에 있는 serializer/deserializer가 기본으로 동작한다.

 

 

contentUsing: Class<? extends JsonSerializer> / Class<? extends JsonDeserializer>

collection/array의 요소를 직렬화/역직렬화 하기 위한 serializer, deserializer의 클래스를 설정한다.

 

 

keyUsing: Class<? extends JsonSerializer> / Class<? extends JsonDeserializer>

Map의 키를 직렬화/역질렬화 하기 위한 serializer, deserializer의 클래스를 설정한다,

 

 

nullsUsing: Class<? extends JsonSerializer> / Class<? extends JsonDeserializer>

null 값을 직렬화/역질렬화 하기 위한 serializer, deserializer의 클래스를 설정한다.

 

as: Class<?>

직렬화/역직렬화 할 타입을 선언할 수 있다. Void로 선언 된 것은 따로 타입이 설정 안 된 것으로 해당 값의 타입을 그대로 사용한다.

기본 값은 Void 이다.

 

keyAs: Class<?>

키를 직렬화/역직렬화 할 타입을 선언할 수 있다. Void로 선언 된 것은 따로 타입이 설정 안 된 것으로 해당 값의 타입을 그대로 사용한다.

기본 값은 Void이다.

 

contentAs: Class<?>

collection/array의 요소를 직렬화/역직렬화 할 타입을 선언할 수 있다. Void로 선언 된 것은 따로 타입이 설정 안 된 것으로 해당 값의 타입을 그대로 사용한다.

기본 값은 Void이다.

 

typing: Typing

@JsonSerializer에만 있는 속성이다.

해당 타입이 런타임 유형에 적용되는지(DYNAMIC), 이미 선언된 유형 중 하나(STATIC)인지 설정할 수 있다.

이에 대해 잘 이해가 안 되긴 하는데 추후 테스트해서 추가 정리를 해봐야겠다.

 

converter: Class<? extends Converter>

@JsonSerializer에만 있는 속성이다.

기본 타입은 Jackson이 쉽게 직렬화할 수 없으므로 Jackson이 쉽게 직렬화 하기 위해 object를 converte하기 위한 converter를 설정한다.

 

contentConverter: Class<? extends Converter>

converter 속성과 비슷한데 List, array, Maps의 값에 사용된다.

property에 어노테이션을 사용했을 때만 적용될 수 있는 속성이라고 한다.

 

 

Custom Serializer/Deserializer

@JsonSerialize, @JsonDeserialize에 넣을 직렬화/역직렬화 클래스는 상황에 맞게 커스텀하게 만들어 사용할 수 있다.

각각 JsonSerializer, JsonDeserializer를 상속 받아서 직렬화/역직렬화할 타입을 넣고 serialize, deserialize 함수를 override해서 안에 원하는 로직을 넣으면 된다.

class TestSerializer: JsonSerializer<String>() {
    override fun serialize(value: String, gen: JsonGenerator, serializers: SerializerProvider?) {
        gen.writeString(${value를 수정한 값})
    }
 }

 

class TestDeserializer: JsonDeserializer<String>() {
    override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): String {
        return p.toString() // deserialize
    }
}

 

 

이렇게 만든 커스텀한 serializer, deserializer는 @JsonSerialize, @JsonDeserialize 어노테이션에 넣어서 사용할 수 있다.

@JsonSerialize(using = TestSerializer::class)

@JsonDeserialize(using = TestDeserializer::class)

 

 

활용예

예를 들어 날짜 데이터를 응답값으로 내려줄 때 데이터 값을 UTC 타임스탬프로 바꿔서 내려주고 싶을 때 아래와 같이 customSerializer를 만든다.

class LocalDateTimeStampSerializer : JsonSerializer<LocalDateTime>() {
    override fun serialize(value: LocalDateTime?, gen: JsonGenerator, serializers: SerializerProvider) {
        if (value == null) {
            gen.writeNull()
        } else {
            try {
                gen.writeNumber(value.atZone(ZoneId.of("UTC")).toInstant().toEpochMilli())
            } catch (e: Exception) {
                log.error("can not serialize date to timestamp!! value: $value")
            }
        }
    }
}

 

 

응답값 클래스 필드에 필요한 부분에 만든 customSeserializer를 명시한다.

data class TestResponse (
    @Schema(description = "제목")
    val title: String,

    @Schema(description = "내용")
    val content: String,
    
    @JsonSrialize(using = LocalDateTimeStampSerializer::class)
    @Schema(description = "생성일")
    val created: LocalDateTime,
    
    @JsonSrialize(using = LocalDateTimeStampSerializer::class)
    @Schema(description = "업데이트일")
    val updated: LocalDateTime
}

 

@RestController
@RequestMapping("/test")
class TestController(
	private val testService: TestService
) {
    @GetMapping("/custom-serialize")
    fun customSerializer(): TestResponse {
        return testService.test()
    }
}

 

 

위와 같이 사용을 하면 created, updated를 로직 내에서는 LocalDateTime 클래스로 사용을 해도 api 응답을 받은 곳에서는 UTC 타임스탬프로 받게 된다.

날짜 형식으로 주고 받았을 경우 글로벌 서비스의 경우는 타임존 때문에 골치를 썩히는 일이 종종 있어서 이렇게 타임스탬프로 변경해서 주고 받기도 한다.

 

 

 

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함