
기존에 Retrofit을 사용해 데이터를 받아오며 Json파일의 변환을 위해 구글에서 개발한 Gson라이브러리를 사용했다.
하지만 Gson라이브러리의 경우 현재 유지/관리만 이루어 지고 있으며, 대규모 기능이 추가되지 않는 상태이다. 또한 Java를 대상으로 하기때문에 Kotlin에서는 DTO클래스에 default value를 설정한 프로퍼티가 있어도 적용되지 않을 수 있으며, null safety기능을 지원하지 않아 사용시 잘못된 동작이 생길 수 있다.
그래서 새로운 Json parser 를 찾아보게 되었다.
Gson의 대체제를 찾아야 겠다는 생각을 하게된 건, 사실 Moshi때문이었다.
기존에 프로젝트를 진행하며, 늘쓰던대로 아무 생각없이 Gson을 사용해서 프로젝트를 진행했는데, 함께 스터디하는 팀원들이 더 이상 Gson은 업데이트도 안되고, 너무 Kotlin친화적이지 못해서 직렬화/역직렬화를 위해 Moshi를 사용했다는 소식을 들었기 때문이다.
그래서 기왕 바꾸는 김에 Moshi가 과연 최고일까 알아보기로 했다.
우선 Moshi는 Retrofit과 Okhttp, Picasso등을 만든 Square사에서 만든 직렬화/역직렬화 라이브러리다.
역시 대단한 것들을 만든 회사에서 개발한 라이브러리답게 Moshi는 Gson에 비해 훨씬 빠르고, Kotlin친화적인 직렬화 라이브러리다. 그럼에도 불구하고 Moshi는 태생적인 한계를 가지고 있다.
우선 라이브러리 자체가 Java를 사용해 개발되다 보니 뒤에 알아볼 Kotlinx Serialization과 비교해보면 비교적 덜 Kotlin친화적이라고 할 수 있다.

그리고 Moshi가 Kotlin의 여러 타입들을 지원하기는 하지만 sealed class를 지원하지 않는다. 물론 서브타입을 지정함으로써 역직렬화가 가능하긴 하다.
Moshi보다 간편하면서도 강력한 성능을 가진 직렬화 라이브러리를 알아보던중 Kotlin 자체적으로 직렬화 라이브러리를 가지고 있다는 것을 알게됐다. 검색을 하다보니 여러가지 사항을 고려했을때 Moshi보다는 Kotlinx Serialization이 Kotlin환경에서 훨씬 유리하다는 것을 알 수 있었다.
그렇다면 왜 Kotlinx Serialization을 선택하게 됐는지 알아보자.
앞서 이야기했듯 Kotlinx Serialization은 모두 Kotlin으로 개발 되었기 때문에 Kotlin친화적이 아닐수가 없는 라이브러리다.
예를들어 다른 직렬화 라이브러리에서는 지원하지않는 Kotlin의 sealed class나 default value등 Kotlin만의 요소들을 지원한다.
Moshi역시 Gson과 비교하여 확실히 향상된 성능을 보여주지만, Kotlinx Serialization의 경우 보다 더 강력한 성능을 보여준다. 특히 크기가 큰 데이터나, sealed class로 이루어진 데이터의 경우 큰 차이를 보였다.
참고 : Moshi vs KotlinX Serialization — the ultimate benchmark
여타 라이브러리가 그렇듯 plugin과 dependency를 추가하고 사용하면 되는데, 보동 직렬화 라이브러리를 Retrofit을 사용해 받아온 데이터를 변환하는데 많이 사용하기 때문에 컨버터 라이브러리도 함께 추가해주면 좋다.
plugins {
kotlin("plugin.serialization") version "1.9.22"
}
dependencies {
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}
데이터를 Json으로 직렬화 하기위해서는 encodeToString, 역직렬화를 위해서는 decodeFromString
명령어를 사용한다.
val data = Project("kotlinx.serialization", "Kotlin")
// data를 Json파일로 직렬화
val string = Json.encodeToString(data)
// 역직렬화를 위해서는 변환해야하는 타입을 제네릭 선언해준다.
val obj = Json.decodeFromString<Project>(string)
println(obj) // Project(name=kotlinx.serialization, language=Kotlin)
Retrofit의 ConverterFactory로도 사용이 가능하다.
val contentType = "application/json".toMediaType()
private val baseURL = "..."
private val retrofit = Retrofit.Builder()
.baseUrl(baseURL)
.addConverterFactory(Json.asConverterFactory(contentType))
.build()
val retrofitClient: Api = retrofit.create(Api::class.java)
Kotlinx Serialization는 효과적인 직렬화/역직렬화를 위해 다양한 기능을 제공하는데, 간단한 기능 2가지를 살펴보자.
Kotlinx Serialization에서 Json객체를 생성하며 람다를 통해 빌더 액션을 지정할 수 있는데 Pretty print를 ture로 지정하면 해당 기능이 활성화된다.
Pretty print를 켜면 출력되는 Json스트링이 보기 이쁜(?) 형태로 바뀐다.
val json = Json { prettyPrint = true } // 빌터 액션을 지정후
val jsonString = json.encodeToString(person)
{"name":"John Doe","age":30,"address":{"street":"1234 Main St","city":"New York","zipCode":"10001"}}
{
"name": "John Doe",
"age": 30,
"address": {
"street": "1234 Main St",
"city": "New York",
"zipCode": "10001"
}
}
역직렬화시 Json스트링에 변환되는 타입에 존재하지 않는 값이 있을 경우 에러가 발생하는데,
이러한 경우 해당 값을 무시하도록 만들어주는 액션이다.
@Serializable
data class Person(
val name: String,
val age: Int
)
val json = Json { ignoreUnknownKeys = true }
val string = """
{
"name": "John Doe",
"age": 30,
"address": {
"street": "1234 Main St",
"city": "New York",
"zipCode": "10001"
}
}
"""
// adress 속성이 없어도 에러가 나지 않는다.
json.decodeFromString<Person>(string)
}
참고 - [Kotlinx serialization] Json 직렬화/역직렬화 - Fast apply #1 - [투덜이의 리얼 블로그:티스토리]