코틀린이 제공하는 직렬화 라이브러리인 kotlinx.serialization를 소개하고, 왜 만들었는지 그리고 어떤 특징을 가지고 있는지 공유합니다.
슬라이드 내 예제 코드는 github 에 공개되어 있습니다.
https://github.com/arawn/kotlinx-serialization-overview
3. !
자바 생태계의 직렬화 도구를 이용한 JSON 직렬화하기
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
class GsonSpec : FunSpec({
val gson = Gson()
test("객체를 JSON으로 직렬화 또는 역직렬화하기") {
val data = Movie("foo", "x", 0.1)
val serialized = gson.toJson(data)
serialized shouldBe """{"title":"foo","director":"x","rating":0.1}"""
val deserialized = gson.fromJson(serialized, Movie::class.java)
deserialized shouldBe data
}
test("리스트를 JSON으로 직렬화 또는 역직렬화하기") {
val data = listOf(Movie("foo", "x", 0.1), Movie("bar", "y", 9.9))
val serialized = gson.toJson(data)
serialized shouldBe """[{"title":"foo","director":"x","rating":0.1},{..}]"""
val javaType = object : TypeToken<List<Movie>>() {}.type
val deserialized = gson.fromJson<List<Movie>>(serialized, javaType)
deserialized shouldBe data
}
})
Kotlin/JVM
4. !
kotlinx.serialization으로 JSON 직렬화하기
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
class SimpleKotlinSerializationSpec : FunSpec({
test("객체를 JSON으로 직렬화 또는 역직렬화하기") {
val data = Movie("foo", "x", 0.1)
val serialized = Json.encodeToString(data)
serialized shouldBe """{"title":"foo","director":"x","rating":0.1}"""
val deserialized = Json.decodeFromString<Movie>(serialized)
deserialized shouldBe data
}
test("리스트를 JSON으로 직렬화 또는 역직렬화하기") {
val data = listOf(Movie("foo", "x", 0.1), Movie("bar", "y", 9.9))
val serialized = Json.encodeToString(data)
serialized shouldBe """[{"title":"foo","director":"x","rating":0.1},{..}]"""
val deserialized = Json.decodeFromString<List<Movie>>(serialized)
deserialized shouldBe data
}
})
Kotlin/JVM
7. !
null이 없었는데요 있었습니다
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
class GsonSpec : FunSpec({
val gson = Gson()
test("역직렬화시 널을 허용하지 않는 속성에 널을 입력해도 예외가 발생하지 않아요") {
val serialized = """{"title":"foo","rating":null}"""
val javaType = object : TypeToken<Movie>() {}.type
val deserialized = gson.fromJson<Movie>(serialized, javaType)
deserialized.title shouldBe "foo"
deserialized.director shouldBe null
deserialized.rating shouldBe 0.0
}
})
data class Movie(
val title: String,
val director: String,
val rating: Double = 1.0
)
Kotlin/JVM
8. !
기본이 안 돼 있네 기본이
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
class GsonSpec : FunSpec({
val gson = Gson()
test("역직렬화시 기본 인자가 설정된 속성을 지원하지 않아요") {
val serialized = """{"title":"foo","director":"x"}"""
val deserialized = gson.fromJson(serialized, Movie::class.java)
deserialized.rating shouldBe 0.0
}
})
data class Movie(
val title: String,
val director: String,
val rating: Double = 1.0
)
Kotlin/JVM
9. !
거 참 말 많네
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
class JacksonSpec : FunSpec({
test("데이터 클래스를 JSON으로 직렬화 또는 역직렬화하기") {
val mapper = JsonMapper.builder().build()
val data = Project("foo", 1.0)
val serialized = mapper.writeValueAsString(data)
serialized shouldBe """{"name":"foo","version":1.0}"""
val deserialized = mapper.readValue(serialized, Project::class.java)
deserialized.name shouldBe data.name
deserialized.version shouldBe data.version
}
})
data class Project @JsonCreator constructor(
@JsonProperty("name") val name: String,
@JsonProperty("version") val version: Double
)
Kotlin/JVM
10. !
거 참 말 많네 second edition
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.module.kotlin.kotlinModule
class JacksonSpec : FunSpec({
val mapper = JsonMapper.builder().addModule(kotlinModule()).build()
test("객체를 JSON으로 직렬화 또는 역직렬화하기") {
val data = Movie("foo", "x", 0.1)
val serialized = mapper.writeValueAsString(data)
serialized shouldBe """{"title":"foo","director":"x","rating":0.1}"""
val javaType = object: TypeReference<Movie>() {}
val deserialized = mapper.readValue(serialized, javaType)
deserialized shouldBe data
}
test("리스트를 JSON으로 직렬화 또는 역직렬화하기") {
val data = listOf(Movie("foo", "x", 0.1), Movie("bar", "y", 9.9))
val serialized = mapper.writeValueAsString(data)
serialized shouldBe """[{"title":"foo","director":"x","rating":0.1},{..}]"""
val javaType = mapper.typeFactory.constructCollectionType(List::class.java, Movie::class.java)
val deserialized = mapper.readValue<List<Movie>>(serialized, javaType)
deserialized shouldBe data
}
})
Kotlin/JVM
13. !
kotlinx.serialization으로 JSON 직렬화하기
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
class SimpleKotlinSerializationSpec : FunSpec({
test("객체를 JSON으로 직렬화 또는 역직렬화하기") {
val data = Movie("foo", "x", 0.1)
val serialized = Json.encodeToString(Movie.serializer(), data)
serialized shouldBe """{"title":"foo","director":"x","rating":0.1}"""
val deserialized = Json.decodeFromString(Movie.serializer(), serialized)
deserialized shouldBe data
}
})
@Serializable
data class Movie(
val title: String,
val director: String,
val rating: Double = 1.0
)
Kotlin/JVM
14. !
컴파일 안전 보장 compile-time safe
import kotlinx.serialization.Serializable
class SimpleKotlinSerializationSpec : FunSpec({
test("컴파일 타임에 직렬화 지원 여부를 확인해요") {
data class User(val userName: String)
@Serializable
data class Project(
val name: String,
val owner: User,
val language: String = "Kotlin"
)
}
})
Kotlin/JVM
15. !
명료함 및 간결함 explicit and concise
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
class SimpleKotlinSerializationSpec : FunSpec({
test("객체를 JSON으로 직렬화 또는 역직렬화하기") {
val data = Movie("foo", "x", 0.1)
val serialized = Json.encodeToString(data)
serialized shouldBe """{"title":"foo","director":"x","rating":0.1}"""
val deserialized = Json.decodeFromString<Movie>(serialized)
deserialized shouldBe data
}
test("리스트를 JSON으로 직렬화 또는 역직렬화하기") {
val data = listOf(Movie("foo", "x", 0.1), Movie("bar", "y", 9.9))
val serialized = Json.encodeToString(data)
serialized shouldBe """[{"title":"foo","director":"x","rating":0.1},{..}]"""
val deserialized = Json.decodeFromString<List<Movie>>(serialized)
deserialized shouldBe data
}
})
Kotlin/JVM
16. !
코틀린 지향 Kotlin-oriented
class SimpleKotlinSerializationSpec : FunSpec({
test("역직렬화시 널을 허용하지 않는 원시타입 속성에 널을 입력되면 예외가 발생해요") {
val exception = shouldThrow<SerializationException> {
Json.decodeFromString<Movie>("""{"title":"foo","director":"x","rating":null}""")
}
exception.message shouldBe """
Unexpected JSON token at offset 43: Failed to parse type 'double' for input 'null'
JSON input: {"title":"foo","director":"x","rating":null}
""".trimIndent()
}
test("역직렬화시 널을 허용하지 않는 속성에 널이 입력되면 예외가 발생해요") {
val exception = shouldThrow<SerializationException> {
Json.decodeFromString<Movie>("""{"title":"foo","rating":0.1}""")
}
exception.message shouldBe """
Field 'director' is required for type with serial name 'Movie', but it was missing
""".trimIndent()
}
test("역직렬화시 기본 인자가 설정된 속성을 지원해요") {
val deserialized = Json.decodeFromString<Movie>("""{"title":"foo","director":"x"}""")
deserialized.rating shouldBe 1.0
}
})
Kotlin/JVM
17. !
다양한 형식 지원 multi-format
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.encodeToByteArray
import kotlinx.serialization.decodeFromByteArray
class SimpleKotlinSerializationSpec : FunSpec({
test("객체를 CBOR 형식으로 직렬화 또는 역직렬화하기") {
val data = Movie("foo", "x", 0.1)
val serialized = Cbor.encodeToByteArray(data)
serialized.toAsciiHexString() shouldBe """
{BF}etitlecfoohdirectoraxfrating{FB}?{B9}{99}{99}{99}{99}{99}{9A}{FF}
""".trimIndent()
val deserialized = Cbor.decodeFromByteArray<Movie>(serialized)
deserialized shouldBe data
}
test("객체를 ProtoBuf 형식으로 직렬화 또는 역직렬화하기") {
val data = Movie("foo", "x", 0.1)
val serialized = ProtoBuf.encodeToByteArray(data)
serialized.toAsciiHexString() shouldBe """
{0A}{03}foo{12}{01}x{19}{9A}{99}{99}{99}{99}{99}{B9}?
""".trimIndent()
val deserialized = ProtoBuf.decodeFromByteArray<Movie>(serialized)
deserialized shouldBe data
}
})
Kotlin/JVM