[SpringBoot] Kotlin 환경에서 JSON 직렬화/역직렬화

tkppp·2022년 6월 6일
1

SpringBoot with Kotlin

목록 보기
12/12

직렬화와 역직렬화

Java에서 말하는 직렬화(Serialization)는 객체를 직렬화하여 전송 가능한 형태로 만드는 것을 의미한다. 객체들의 데이터를 연속적인 데이터로 변형하여 Stream을 통해 데이터를 읽도록 해준다.
이것은 주로 객체들을 통째로 파일로 저장하거나 전송하고 싶을 때 주로 사용된다.

역직렬화(Deserialization)는 직렬화된 파일 등을 역으로 직렬화하여 다시 객체의 형태로 만드는 것을 의미한다. 저장된 파일을 읽거나 전송된 스트림 데이터를 읽어 원래 객체의 형태로 복원한다.

예시

요청 본문으로 들어온 JSON 데이터를 자바 객체로 파싱하는 것 => 역직렬화
응답 본문에 자바 객체를 JSON 으로 파싱하는 것 => 직렬화

스프링부트의 JSON 파싱

요청 본문과 응답 본문과 같은 부분은 Jackson 라이브러리를 기본으로 JSON 파싱을 수행

Java/Kotlin JSON 파싱 라이브러리

Jackson

설정

스프링부트는 기본적으로 Jackson 라이브러리가 포함되어 있으므로 의존성 설정은 필요하지 않다.

사용시 ObjectMapper 객체를 생성해서 사용한다. 코틀린 환경에서는 registerKotlinModule() 메소드를 사용해야만 Pair 같은 코틀린 객체를 파싱할 수 있다.

val mapper = ObjectMapper().registerKotlinModule()

직렬화

writeValueAsString(object) 메소드를 사용해 객체를 직렬화 한다.

역직렬화

readValue<T>(jsonString) 메소드를 사용해 Json을 객체로 역직렬화 한다.

테스트


data class TripleJson (
    val fullname: Triple<String, String, Int>
)

@Test
@DisplayName("JSON 직렬화/역직렬화 테스트 - Jackson")
fun jsonTestWithJackson() {
    val mapper = ObjectMapper().registerKotlinModule()
    val me = TripleJson(Triple("Park", "Taekyeong", 26))
    val serialize = mapper.writeValueAsString(me)
    println(serialize)

    val deserialize = mapper.readValue<TripleJson>(serialize)
    println(deserialize)
}

/* 테스트 결과
{"fullname":{"first":"Park","second":"Taekyeong","third":26}}
PairJson(fullname=(Park, Taekyeong, 26))
*/

Kotlin JSON 파싱 라이브러리

Kotlinx.Serialization

kotlin serialization 은 단순 json 라이브러리가 아니다. 라이브러리 이름에서부터 느껴지듯이 kotlin 객체 직렬화를 위한 라이브러리다.

또 단순 라이브러리가 아니라 컴파일러 레벨에서 동작하는 컴파일러 플러그인이기 때문에 serialization 을 이용할때는 gradle 에 플러그인 설정을 해줘야한다.

플러그인 설정 시 JSON 직렬화 / 역직렬화가 필요할 시 기본으로 설정되있는 Jackson 라이브러리가 아니라 Kotlinx.Serialization 으로 파싱이 이루어진다. 단 Kotlinx.Serialization 로 파싱에 실패할 경우 Jackson 라이브러리로 파싱이 이루어지므로 파싱이 성공하더라도 Kotlinx.Serialization 으로 파싱된 것이 아닐 수 있음에 유의

설정

  • 플러그인 및 의존성 설정
plugins {
    kotlin("jvm") version "1.6.21" // or kotlin("multiplatform") or any other kotlin plugin
    kotlin("plugin.serialization") version "1.6.21"
}

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.3")
}

더 자세한 내용은 공식 문서 참조

  • 직렬화 / 역직렬화 할 객체에 @Serilazable 어노테이션 작성
@Serializable
data class TripleJson (
    val fullname: Triple<String, String, Int>
)

직렬화

Json.encodeToString(object) 확장 함수를 사용해 객체를 직렬화 한다.

역직렬화

Json.decodeFromString<T>(jsonString) 확장 함수를 사용해 Json을 객체로 역직렬화 한다.

테스트

@Test
@DisplayName("JSON 직렬화/역직렬화 테스트 - Kotlinx.serialization")
fun jsonTest() {
	val me = TripleJson(Triple("Park", "Taekyeong", 26))
    val serialize = Json.encodeToString(me)
    println(serialize)

	val deserialize = Json.decodeFromString<TripleJson>(serialize)
    println(deserialize)
}

/* 테스트 결과
{"fullname":{"first":"Park","second":"Taekyeong","third":26}}
PairJson(fullname=(Park, Taekyeong, 26))
*/

enum 파싱

열거형 클래스의 name과 JSON의 문자열 데이터가 일치한다면 별도의 설정없이 파싱이 된다.

문제는 열거형 클래스의 기본 name이 아닌 다른 변수와 JSON의 문자열 데이터를 매칭하려하는 경우에는 별도의 설정이 필요하다

문제상황

enum class KboTeam(val fullName: String, val code: String){
    SS("삼성", "SS"), KW("키움", "WO"), NX("넥센", "WO"),
    HH("한화","HH"), LG("LG", "LG"), LT("롯데","LT"),
    SSG("SSG", "SK"), SK("SK", "SK"), HT("KIA", "HT"),
    OB("두산", "OB"), NC("NC", "NC"), KT("KT", "KT");
}

팀 코드는 같지만 구단명 변경 같은 이유로 해서 구단 이름이 다른 경우가 있고 한글은 enum 기본 이름으로 사용할 수 없기 때문에 별도의 변수에 JSON 문자열 데이터를 매핑해야되는 상황이었다.

해결

enum class KboTeam(@JsonValue val fullName: String, val code: String){
    SS("삼성", "SS"), KW("키움", "WO"), NX("넥센", "WO"),
    HH("한화", "HH"), LG("LG",  "LG"), LT("롯데", "LT"),
    SSG("SSG", "SK"), SK("SK", "SK"), HT("KIA", "HT"),
    OB("두산", "OB"), NC("NC", "NC"), KT("KT", "KT");

    companion object {
        @JvmStatic
        @JsonCreator
        fun set(team: String) = values().find { it.fullName == team }
    }
}

@JsonValue 어노테이션으로 매핑할 변수를 지정하고 companion object 와 @JvmStatic 어노테이션을 통해 정적 메소드를 정의한다. 여기서 @JsonCreator 어노테이션을 사용해 매핑에 사용될 메소드를 정의하면 원하는 대로 파싱이 이루어진다.

Pair, Triple 과 같은 Kotlin 특화 객체 파싱

최근 파이썬을 주로 사용했다보니 튜플과 리스트가 자연스럽게 구조분해할당이 되는 것처럼 Pair과 Triple도 리스트와 매핑될거라 생각하고 코드를 작성했지만 Pair과 Triple은 first, second, third(Triple 한정)를 프로퍼티로 가지는 객체와 매핑되기 때문에 실패했다. 결국 리스트로 타입을 변경하여 문제를 해결하였다.

참조

https://tourspace.tistory.com/357
https://smelting.tistory.com/68
https://multifrontgarden.tistory.com/285

1개의 댓글

comment-user-thumbnail
2024년 1월 24일

어 이건 직렬화라고 하기에는, 너무 json 에 가까운듯합니다?
https://medium.com/@lunay0ung/basics-%EC%A7%81%EB%A0%AC%ED%99%94-serialization-%EB%9E%80-feat-java-2f3eb11e9a8

답글 달기