kotlinx.serialization tips

kotlinx.serializationでやりたいことを調査したので、その結果をtipsとしてまとめました

versionはこちらを使っています

```kotlin
kotlin("plugin.serialization") version "1.9.22"
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
```
## 1. 日時をZonedDateTimeに変換したい
Custom Serializerを作って、Serializable AnnotationでCustom Serializerを指定してあげればOKです

https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#custom-serializers

```kotlin: Custom Serializer
object ZonedDateTimeSerializer : KSerializer<ZonedDateTime> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ZonedDateTime", PrimitiveKind.STRING)
    override fun serialize(encoder: Encoder, value: ZonedDateTime) {
        encoder.encodeString(value.toString())
    }
    override fun deserialize(decoder: Decoder): ZonedDateTime = ZonedDateTime.parse(decoder.decodeString())
}
```
```kotlin: Data Class
@Serializable
data class Response(
    @Serializable(ZonedDateTimeSerializer::class)
    val dateUtc: ZonedDateTime,
    @Serializable(ZonedDateTimeSerializer::class)
    val dateJst: ZonedDateTime,
)
```
```kotlin: Execute
val json =
    """
        {
          "dateUtc": "2024-02-17T02:03:26Z",
          "dateJst": "2024-02-17T11:03:26+09:00"
        }
    """
val response: Response = Json.decodeFromString<Response>(json)
plintln(response.toString())

val string = Json.encodeToString(Response.serializer(), response)
plintln(string)
```
```text: Result
Response(dateUtc=2024-02-17T02:03:26Z, dateJst=2024-02-17T11:03:26+09:00)

{"dateUtc":"2024-02-17T02:03:26Z","dateJst":"2024-02-17T11:03:26+09:00"}
```


## 2. Sealed Interfaceを実装したData Classに変換したい
Polymorphismに対応しているため簡単にできます

https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#polymorphism

SerialName Annotationでproperty typeのvalueを指定してあげればOKです
※ defaultではpropertyはtypeである必要があります(customのやり方は4でかきます)

```kotlin: Data Class
@Serializable
data class Response(
    val firstNote: Note,
    val secondNote: Note,
)

@Serializable
sealed interface Note{
    val text: String
}

@Serializable
@SerialName("A")
data class NoteA(
    override val text: String,
) : Note

@Serializable
@SerialName("B")
data class NoteB(
    override val text: String,
    val number: Int,
) : Note
```
```kotlin: Execute
val json =
    """
    {
      "firstNote": {
        "text": "text",
        "number": 1,
        "type": "B"
      },
      "secondNote": {
        "text": "text",
        "type": "A"
      }
    }
    """
val response: Response = Json.decodeFromString<Response>(json)
plintln(response.toString())
```
```text: Result
Response(firstNote=NoteB(text=text, number=1), secondNote=NoteA(text=text))
```


## 3. 2をlistで持ちたい
すみません、分けたのですが、2と全く同じです

```kotlin: Data Class
@Serializable
data class Response(
    val notes: List<Note>,
)

@Serializable
sealed interface Note{
    val text: String
}

@Serializable
@SerialName("A")
data class NoteA(
    override val text: String,
) : Note

@Serializable
@SerialName("B")
data class NoteB(
    override val text: String,
    val number: Int,
) : Note
```
```kotlin: Execute
val json =
    """
    {
      "notes": [
        {
          "text": "text!!",
          "number": 22,
          "type": "B"
        },
        {
          "text": "text",
          "type": "A"
        }
      ]
    }
    """

val response: Response = Json.decodeFromString<Response>(json)
plintln(response.toString())
```
```text: Result
Response(notes=[NoteB(text=text!!, number=22), NoteA(text=text)])
```

## 4. 2と3のpropertyをcustomしたい

sealed interfaceにJsonClassDiscriminator Annotationでpropertyのkeyを指定してあげればOKです
※ sealed interface だと何故か型解決できなかったため、この場合はsealed classにしてください

```kotlin: Data Class
@Serializable
data class Response(
    val note: Note,
    val notes: List<Note>,
)

@OptIn(ExperimentalSerializationApi::class)
@Serializable
@JsonClassDiscriminator("customType")
sealed class Note{
   abstract val text: String
}

@Serializable
@SerialName("A")
data class NoteA(
    override val text: String,
) : Note()

@Serializable
@SerialName("B")
data class NoteB(
    override val text: String,
    val number: Int,
) : Note()
```
```kotlin: Execute
val json =
    """
    {
        "note": {
            "text": "textB",
            "customType": "B",
            "number": 99
        },
      "notes": [
        {
          "text": "text!!",
          "number": 22,
          "customType": "B"
        },
        {
          "text": "text",
          "customType": "A"
        }
      ]
    }
    """

val response: Response = Json.decodeFromString<Response>(json)
plintln(response.toString())
```
```text: Result
Response(note=NoteB(text=textB, number=99), notes=[NoteB(text=text!!, number=22), NoteA(text=text)])
```

## 5. 2と3でpropertyに未知のvalueがあったら無視したい
調べたのですが、Annotationでは解決できなさそうでした
（もし、ご存知の方がいましたら教えてください🙇🏻‍♂️）
そのため、Custom Serializerを作成して、未知のtypeはnullを返すようにしてあげればよさそうです
（PolymorphismのAnnotationは削除します）
```kotlin: Custom Serializer
object NoteSerializer : KSerializer<Note?> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Note", PrimitiveKind.STRING)
    override fun deserialize(decoder: Decoder): Note? {
        val input = decoder as JsonDecoder
        val tree = input.decodeJsonElement()
        return when (tree.jsonObject["type"]?.jsonPrimitive?.contentOrNull) {
            "A" -> Json.decodeFromJsonElement(NoteA.serializer(), tree)
            "B" -> Json.decodeFromJsonElement(NoteB.serializer(), tree)
            else -> null
        }
    }

    override fun serialize(encoder: Encoder, value: Note?) {
        when (value) {
            is NoteA -> encoder.encodeSerializableValue(NoteA.serializer(),value)
            is NoteB -> encoder.encodeSerializableValue(NoteB.serializer(),value)
            else -> {/* noop */}
        }
    }
}
```
```kotlin: Data Class
@Serializable
data class Response(
    val note1: Note? = null,
    val note2: Note? = null,
    val notes: List<Note?>,
)

@Serializable(with = NoteSerializer::class)
sealed class Note {
    abstract val text: String
    abstract val type: String
}

@Serializable
data class NoteA(
    override val text: String,
    override val type: String,
) : Note()

@Serializable
data class NoteB(
    override val text: String,
    override val type: String,
    val number: Int,
) : Note()
```
```kotlin: Execute
val json = 
    """
    {
        "note1": {
            "text": "textB",
            "type": "B",
            "number": 99
        },
        "note2": {
            "text": "textX",
            "type": "X",
            "number": 87
        },
      "notes": [
        {
          "text": "text!!",
          "number": 22,
          "type": "Z"
        },
        {
          "text": "text",
          "type": "A"
        }
      ]
    }
    """

val response: Response = Json.decodeFromString<Response>(json)
plintln(response.toString())

val string = Json.encodeToString(Response.serializer(), response)
plintln(string)
```
```text: Result
Response(note1=NoteB(text=textB, type=B, number=99), note2=null, notes=[null, NoteA(text=text, type=A)])

{"note1":{"text":"textB","type":"B","number":99},"notes":[null,{"text":"text","type":"A"}]}
```

filteringすればいいですが、listにnullが入ってくるのは正直ちょっと嫌ですね・・
listもCustom Serializerを作ればいいのかな？