본문으로 바로가기
반응형

이 글은 하기 링크를 기준으로 설명합니다.

github.com/Kotlin/kotlinx.serialization/blob/master/docs/builtin-classes.md

또한 예제에서 사용된 LogInfo(TAG) { "print log"}의 표현은 안드로이드의 Log.d(TAG, "print log")와 동일한 표현입니다.

 

이 글은 여러개의 series로 구성되었습니다.

2020/10/22 - [개발이야기/Kotlin] - [Kotlinx serialization] Json 직렬화/역직렬화 - Fast apply #1

2020/11/03 - [개발이야기/Kotlin] - [Kotlinx serialization] 기본 사용법#2

2020/11/27 - [개발이야기/Kotlin] - [Kotlinx serialization] Json 직렬화/역직렬화 - Serializers #4

2020/12/04 - [개발이야기/Kotlin] - [Kotlinx serialization] Json 직렬화/역직렬화 -Polymorphism #5

2020/12/15 - [개발이야기/Kotlin] - [Kotlinx serialization] Json 직렬화/역직렬화 -JSON features #6


Kotlin serialization에는 primitive type과 string을 포함하여 기본 collection들과 같은 Kotlin 일부 class들에 대한 대한 serailization이 내장되어 있습니다.

이번 포스팅에서는 기본적인 serialization 동작에 대해서 알아봅니다.

Primitives

Kotlin serialization은 다음 10개의 primitive type을 갖습니다.

  • Boolean
  • Byte
  • Short
  • Int
  • Long
  • Float
  • Double
  • Char
  • String
  • enum

Numbers

모든 정수 타입과 소숫점 타입들은 serialization이 될 수 있습니다.

@Serializable
data class MyNumbers(val count: Int, val pi: Double, val bigValue: Long)

private fun makeNumbersJson() {
    val data = MyNumbers(62, PI, 0x1CAFE2FEED0BABE0)

    val customJson = Json { prettyPrint = true }
    val dataJson = customJson.encodeToString(data)
    LogInfo(TAG) { dataJson }
}

결과는 원하는데로 잘 표현됩니다.

{
    "count": 62,
    "pi": 3.141592653589793,
    "bigValue": 2067120338512882656
}

Long numbers as string

위에서 사용된 long 값은 Kotlin serialization을 이용하면 문제없이 parsing할수 있습니다만 Native Java Script를 이용하면 지원하는 숫자의 범위가 Kotlin의 Long보다 작기 때문에 값이 잘리게 됩니다.

따라서 이를 회피하기 위해 long값의 경우 string으로 치환하여 사용합니다.

이를 위해 LongAsStringSerializer를 이용합니다.

@Serializable
data class MyNumbers(val count: Int, 
                     val pi: Double,
                     @Serializable(with = LongAsStringSerializer::class) val bigValue: Long)

private fun makeNumbersJson() {
   ...
}

{
    "count": 62,
    "pi": 3.141592653589793,
    "bigValue": "2067120338512882656"
}

Enum

enum은 기본적으로 @Serializable을 붙이지 않아도 정상적으로 처리 됩니다.

enum class PanSpeed {
    LOW,
    MIDDLE,
    HIGH
}

@Serializable
data class AirConditioner(val manufacturer: String, val panSpeed: PanSpeed)

private fun makeAirJson() {
    val pan = AirConditioner("LG", PanSpeed.LOW)

    val customJson = Json { prettyPrint = true }
    val jsonStr = customJson.encodeToString(pan)
    LogInfo(TAG) { jsonStr }

    val dataFromJson = customJson.decodeFromString<AirConditioner>(jsonStr)
    LogInfo(TAG) { dataFromJson.toString() }
}

{
    "manufacturer": "LG",
    "panSpeed": "LOW"
}

AirConditioner(manufacturer=LG, panSpeed=LOW)

json의 encoding과 decoding이 정상적으로  된것을 확인할 수 있습니다.

그리고  PanSpeed에는 @Serializable을 붙이지 않았습니다.

다만 enum값 자체를 넣지 않고 특정 값을 넣기위해서는 @SerialName을 사용합니다.

    @Serializable
    enum class PanSpeed {
        LOW,
        @SerialName("Normal Speed") MIDDLE,
        HIGH
    }

    @Serializable
    data class AirConditioner(val manufacturer: String, val panSpeed1: PanSpeed, val panSpeed2: PanSpeed)

    private fun makeAirJson() {
        val pan = AirConditioner("LG", PanSpeed.LOW, PanSpeed.MIDDLE)

        val customJson = Json { prettyPrint = true }
        val jsonStr = customJson.encodeToString(pan)
        LogInfo(TAG) { jsonStr }

        val dataFromJson = customJson.decodeFromString<AirConditioner>(jsonStr)
        LogInfo(TAG) { dataFromJson.toString() }
    }

enum의 값이 MIDDLE인 경우 "Normal Speed"로 치환하여 json을 생성하도록 합니다.

{
    "manufacturer": "LG",
    "panSpeed1": "LOW",
    "panSpeed2": "Normal Speed"
}
AirConditioner(manufacturer=LG, panSpeed1=LOW, panSpeed2=MIDDLE)

결과에서 보듯이 json에서는 "MIDDLE" 값이 "Normal Speed"로 변경되어 encode 되었으며, decoding은 "MIDDLE"로 정상 처리되는 것을 확인할 수 있습니다.

(만약 @Serializable을 enum class에 붙지지 않은 경우 complie 오류 없이 "MIDDLE"값으로 json이 생성됩니다.

Composites

기본 타입의 모음으로 구성된 타입들 역시 serialize가 가능합니다.

Pair, Triple

Kotlin 기본 라이브러리에서 지원하는 Pair와 Triple 모두 serialize를 지원합니다.

private fun makeJson() {
    val pair = 1 to "one"      

    val customJson = Json { prettyPrint = true }
    val jsonStr = customJson.encodeToString(pair)
    LogInfo(TAG) { jsonStr }

    val dataFromJson = customJson.decodeFromString<Pair<Int, String>>(jsonStr)
    LogInfo(TAG) { dataFromJson.toString() }
}

{
    "first": 1,
    "second": "one"
}
(1, one)

Pair인 경우 "first" / "second"로 구분되어 json이 생성됩니다.

private fun makeJson() {
    val triple = Triple(2, "two", "second")

    val customJson = Json { prettyPrint = true }
    val jsonStr = customJson.encodeToString(triple)
    LogInfo(TAG) { jsonStr }

    val dataFromJson = customJson.decodeFromString<Triple<Int, String, String>>(jsonStr)
    LogInfo(TAG) { dataFromJson.toString() }
}

{
    "first": 2,
    "second": "two",
    "third": "second"
}
(2, two, second)

Triple인 경우 "first" / "second" / "third" 로 구분되어 json이 생성됩니다.

Kotlin standard library의 모든 class를 지원하지는 않습니다. 그중 ranges 나 RegeX같은 클래스들은 현재는 지원하지 않지만 추후 지원 예정이라고 합니다.

List

Primitive나 Serializable 한 클래스들에 대한 List역시 json array로 표현이 가능합니다.

private fun makeJson() {
    val data1 = mutableListOf("one", "two")
    val data2 =  mutableListOf(Data(1,"aaa"), Data(2,"bbb"))

    val customJson = Json { prettyPrint = true }
    val jsonStr1 = customJson.encodeToString(data1)
    val jsonStr2 = customJson.encodeToString(data2)
    LogInfo(TAG) { jsonStr1 }
    LogInfo(TAG) { jsonStr2 }

    val dataFromJson1 = customJson.decodeFromString<List<String>>(jsonStr1)
    LogInfo(TAG) { dataFromJson1.toString() }
    val dataFromJson2 = customJson.decodeFromString<List<Data>>(jsonStr2)
    LogInfo(TAG) { dataFromJson2.toString() }
}

[
    "one",
    "two"
]

[
    {
        "no": 1,
        "name": "aaa"
    },
    {
        "no": 2,
        "name": "bbb"
    }
]

[one, two]
[Data(no=1, name=aaa), Data(no=2, name=bbb)]

json <-> data class로 잘 변환됨을 확인할 수 있습니다.

Set and other collections

list의 이외의 set 같은 다른 collection들도 json array로 표현됩니다.

private fun makeJson() {
    val data = setOf("one", "two")

    val customJson = Json { prettyPrint = true }
    val jsonStr = customJson.encodeToString(data)
    LogInfo(TAG) { jsonStr }

    val dataFromJson = customJson.decodeFromString<List<String>>(jsonStr)
    LogInfo(TAG) { dataFromJson.toString() }
}

[
    "one",
    "two"
]

[one, two]

json 형태만 보면 list나 set의 경우가 구분되지 않습니다.

따라서 decoding 할 때는 어떤 type으로 decoding 할지를 Generic으로 명시해 해당 collection의 type에 따라 데이터가 생성됩니다.

    @Serializable
    data class Data(val listData: List<String>, val setData: Set<String>)

    private fun makeJson() {
        val customJson = Json { prettyPrint = true }
        val jsonStr = """
            {
              "listData": ["one", "one"],
              "setData": ["one", "one"]
             }
        """

        val dataFromJson = customJson.decodeFromString<Data>(jsonStr)
        LogInfo(TAG) { dataFromJson.toString() }
    }

 

같은 json array를 하나는 List, 남은 하나는 Set에 넣습니다.

Set의 경우 중복된 값이 들어갈 수 없기 때문에 decoding후 채워진 데이터는 아래와 같습니다.

Data(listData=[one, one], setData=[one])

Map

map역시 serialize가 가능합니다.

이때 map의 key가 json의 key가 됩니다.

다만 json의 key는 항상 string이기 때문에 map 의 key가 number라도 string으로 사용하며, 만약 composite type 이라면 serialize가 불가능 합니다.

private fun makeJson() {
    val map = mapOf(1 to "one", 2 to "two")

    val customJson = Json { prettyPrint = true }
    val jsonStr = customJson.encodeToString(map)
    LogInfo(TAG) { jsonStr }

    val dataFromJson = customJson.decodeFromString<Map<Int, String>(jsonStr)
    LogInfo(TAG) { dataFromJson.toString() }
}

{
    "1": "one",
    "2": "two"
}

{1=one, 2=two}

json의 key / value가 map의 key / value와 1:1로 맵핑됩니다.

만약 value로 composite type을 사용 한다면 아래와 같이 변환됩니다.

@Serializable
data class Data(val no: Int, val job: String)

private fun makeJson() {
    val map = mapOf(
        "John" to Data(1,"carpenter"),
        "Smith" to Data(2, "banker"))

    val customJson = Json { prettyPrint = true }
    val jsonStr = customJson.encodeToString(map)
    LogInfo(TAG) { jsonStr }

    val dataFromJson = customJson.decodeFromString<Map<String, Data>>(jsonStr)
    LogInfo(TAG) { dataFromJson.toString() }
}

value에 class를 넣는다면 value 자체가 json object로 변환되어 json에 추가 됩니다.

{
    "John": {
        "no": 1,
        "job": "carpenter"
    },
    "Smith": {
        "no": 2,
        "job": "banker"
    }
}

{John=Data(no=1, job=carpenter), Smith=Data(no=2, job=banker)}

Unit and Singleton object

Unit은 Kotlin에 포함된 기본 singleton object로 singleton 입니다. singleton object는 유일하게 하나의 instance만 가지기 때문에 serializable 하기는 하나 빈값으로 처리 됩니다.

아마도 encoding은 가능하다 할지라도, decoding이 불가능하므로 empty structure로 처리하지 않았을까 예상해 봅니다.

따라서 Unit 역시 empty structure로 처리 됩니다.

@Serializable
object SingletonObject {
    val no = 1
}

private fun makeJson() {
    val jsonStr = Json.encodeToString(SingletonObject)
    val unitStr = Json.encodeToString(Unit)
    LogInfo(TAG) { jsonStr }
    LogInfo(TAG) { unitStr }
}

{}
{}

 

반응형