Kotlin Serialization導入時に詰まったポイント

## はじめに
[Kotlin Serialization](https://github.com/Kotlin/kotlinx.serialization)を新規Projectに追加した際に、漏れていて調べることになった部分について紹介します。

## Kotlin Serializationとは
> Kotlin Serialization is a cross-platform and multi-format framework for data serialization—converting trees of objects to strings, byte arrays, or other serial representations and back. Kotlin Serialization fully supports and enforces the Kotlin type system, making sure only valid objects can be deserialized.

> Kotlin Serializationは、データシリアライゼーションのためのクロスプラットフォームおよびマルチフォーマットフレームワークであり、オブジェクトのツリーを文字列、バイト配列、または他のシリアル表現に変換して戻します。Kotlin SerializationはKotlinの型システムを完全にサポートし、強制するため、有効なオブジェクトだけがデシリアライズできます。(DeepL翻訳)

その他にJSONパーサー機能を提供してくれるライブラリとしては、[Gson](https://github.com/google/gson)や[Moshi](https://github.com/square/moshi)があります。

Gsonは今では[Gson is deprecated.](https://www.reddit.com/r/androiddev/comments/ejjroe/comment/fcz3v0d/?utm_source=share&utm_medium=web2x)が有名ですね。MoshiはそのReplace先としてよく利用されていた印象です。
Kotlin SerializationはMultiplatformに対応しているフレームワークであることや、[Navigation 2.8.0-alpha08](https://developer.android.com/jetpack/androidx/releases/navigation#2.8.0-alpha08)において、Kotlin Serializationを使ってJetpack Composeで型安全なナビゲーションが可能になったことからも、多くのエンジニアが採用するシリアリゼーションライブラリと言えそうです。
直近では[savedstate1.3.0-alpha05](https://developer.android.com/jetpack/androidx/releases/savedstate#1.3.0-alpha05)で`SavedState`もサポートするようになり、活用の幅が広がっています。

## 導入方法
今回はVersion Catalogでの導入方法を紹介します

### serialization pluginの設定

```gradle/libs.versions.toml
[versions]
kotlin = "2.0.20"

[plugins]
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
```

```app/build.gradle.kts
plugins {
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.serialization)
}
```

### JSON ライブラリへの依存関係の追加
- 使用しているKotlinのVersionと、追加しようとしているKotlin SerializationのVersionに問題ないかは[Releases](https://github.com/Kotlin/kotlinx.serialization/releases)で確認してください

```gradle/libs.versions.toml
[versions]
kotlinxSerialization = "1.7.3"
```

```app/build.gradle.kts
dependencies {
    implementation("libs.kotlinx.serialization.json")
}
```

:::note info
[補足]
インターネットに接続し、APIを叩けるようにする場合、Kotlin Serializationと同時に導入するライブラリとして[Retrofit](https://github.com/square/retrofit)があると思います。

[Get data from the internet (CodeLab)](https://developer.android.com/codelabs/basic-android-kotlin-compose-getting-data-internet#7)ではRetrofitと連携するサードパーティライブラリとして、[Kotlin Serialization Converter](https://github.com/JakeWharton/retrofit2-kotlinx-serialization-converter#kotlin-serialization-converter)が紹介されています(執筆時点)。
このライブラリは[Retrofit2.10.0](https://github.com/square/retrofit/releases/tag/2.10.0)にて取り込まれ、`Public archive`になっているため導入は不要です。
:::

## デフォルト引数を指定したdata classがうまくSerializeされない

### 想定される状況
- APIのレスポンス/リクエストBodyに特定の引数が含まれておらず、`200`が返ってこない

### 結論
デフォルト引数は、デフォルトではJSONにエンコードされないため

https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/basic-serialization.md#defaults-are-not-encoded-by-default

Jsonに`encodeDefaults`を`true`に設定するか、

```kotlin
val format = Json { encodeDefaults = true }

@Serializable
class Project(
    val name: String,
    val language: String = "Kotlin",
    val website: String? = null
)

fun main() {
    val data = Project("kotlinx.serialization")
    println(format.encodeToString(data))
}
```
`@EncodeDefault`をつけることでエンコード対象にできます。
```kotlin
@Serializable
@OptIn(ExperimentalSerializationApi::class)
data class Project(
    val name: String,
    @EncodeDefault val language: String = "Kotlin"
)
```
:::note warn
デフォルト値を毎回シリアライズ対象することで、データサイズが大きくなるのを防ぐための設定です。
特に、デフォルト値がnullに設定されている場合に、対応するプロパティへの書き込みを回避できます。
本当にデフォルト引数として含めなくてはならないパラメーターかどうか、要件に沿って検討してください。
:::

### 補足
[EncodeDefault.Mode](https://kotlinlang.org/api/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-encode-default/-mode/)パラメータを利用して、上記の設定等がされていても、エンコード対象から外す設定にすることもできます。

```kotlin
@Serializable
@OptIn(ExperimentalSerializationApi::class
data class User(
    val name: String,
    @EncodeDefault(EncodeDefault.Mode.NEVER) val projects: List<Project> = emptyList()
)
```


## 難読化を有効にしたビルドが失敗する

### エラー

```
kotlinx.serialization.SerializationException: Serializer for class '...' is not found. 
Please ensure that class is Mark as '@Serializable' and that serialization compiler plugin is applied.
```

### 結論
- `proguard-rules.pro`ファイルで`@Serializable`がついたクラスを難読化対象から外す
    - [README](https://github.com/Kotlin/kotlinx.serialization?tab=readme-ov-file#android)
    - [Serialization with proguard](https://github.com/Kotlin/kotlinx.serialization/issues/2385)

```app/proguard-rules.pro
-keep, allowobfuscation, allowoptimization, allowaccessmodification @kotlinx.serialization.Serializable class *
```

### 補足
各設定項目は以下のような役割があります。詳細は[ProGuard manual](https://www.guardsquare.com/manual/configuration/usage)を確認してください。
- Keep Options
  - `-keep`
    - 指定したクラスやクラスメンバーを難読化対象から除外する

- Keep Option Modifiers
  - `allowoptimization`
      - `-keep`オプションで指定したクラス、メソッド等に対して最適化処理を許可する
  - `allowobfuscation`
      - `-keep`オプションで指定したクラス、メソッド等に対し難読化処理を許可する

- Optimization Options
  - `allowaccessmodification`
      - 最適化処理中に、各クラスやメンバー変数のアクセス修飾子を拡張する
          - 最適化ステップの結果を改善する可能性がある
         - ライブラリとして使用されるコードに対して最適化をする場合は、本オプションを指定しないほうが良い

## まとめ
公式ドキュメントに全部書いているから、隅々読もうね！