kotlinx.serialization の JSON 周りの仕様まとめ

## Kotlin Serialization とは

[GitHub リポジトリ](https://github.com/Kotlin/kotlinx.serialization)

Kotlin のシリアライズ専用ライブラリ。単なるライブラリではなく Kotlin コンパイラにバンドルされているコンパイラプラグインであるため、実際に動かすまでが少し大変。

今回は JSON 周りの仕様に限定して書く。

## TL;DR

Int や String? などの基本的な型だけでなく、Map、Set、Array、List も正しく変換される。独自の data class も `@Serializable` を付けるだけで対応できる。

## 具体例

```kotlin
// 基本形
@Serializable
data class Data(val id: Int)

val data = Data(1)
val json = Json.encodeToString(data)
assertEquals("""{"id":1}""", json)
```

```kotlin
// data class でなくても良い
// クラスの場合は構造が同じでも等価ではない
@Serializable
class Data(val id: Int)

val data = Data(1)
val json = Json.encodeToString(data)
assertEquals("""{"id":1}""", json)
```

```kotlin
@Serializable
data class Data(val id: Int = 1)

// 代入する値が初期値と等しければ文字列として出力されない
val data = Data(1)
val json = Json.encodeToString(data)
assertEquals("""{}""", json)

// 文字列として出力したい場合は {encodeDefaults = true} を設定する
val data = Data(1)
val json = Json {encodeDefaults = true}.encodeToString(data)
assertEquals("""{}""", json)
```

```kotlin
@Serializable
data class Data(val id: Int?)

// null は null と出力される
val data = Data(null)
val json = Json.encodeToString(data)
assertEquals("""{"id":null}""", json)
```

```kotlin
@Serializable
data class Data(val id: Int?)

// null 許可であってもキーが存在しなければ例外が発生する
val json = """{}"""
val runnable = { Json.decodeFromString<Data>(json); Unit }
assertThrows(SerializationException::class.java, runnable)
```

```kotlin
@Serializable
data class Data(val a: List<Int>)

// JSON 配列は正しく変換される
val json = """{"a":[1]}"""
val data = Json.decodeFromString<Data>(json)
assertEquals(Data(listOf(1)), data)
```

```kotlin
// Array<T> は構造が同じでも等価でないためプロパティに持つべきではない
// 本来は List<T> で持つべきである
// 今回は「JSON 文字列を Array<T> に変換できるか」に焦点を当てているため許容する
@Serializable
data class Data(val a: Array<Int>)

// Array<T> に変換することも可能
val json = """{"a":[1]}"""
val data = Json.decodeFromString<Data>(json)
assertArrayEquals(arrayOf(1), data.a)
```

```kotlin
// JSON 配列を List<T> に変換することが可能
val json = """[1,2,3]"""
val data = Json.decodeFromString<List<Int>>(json)
assertEquals(listOf(1, 2, 3), data)
```

```kotlin
// Serializable な data class の入れ子
@Serializable
data class Child(val id: Int)

@Serializable
data class Parent(val child: Child)

// 意図した通りに変換される
val data = Parent(Child(1))
val json = Json.encodeToString(data)
assertEquals("""{"child":{"id":1}}""", json)

// 正しく変換される
val json = """{"child":{"id":1}}"""
val data = Json.decodeFromString<Parent>(json)
assertEquals(Parent(Child(1)), data)
```

```kotlin
// Map<T, S> も正しく変換される
val map = mapOf<String, String>("a" to "1")
val json = Json.encodeToString(map)
assertEquals("""{"a":"1"}""", json)
```

```kotlin
// Set<T> は JSON 配列に変換される
val map = setOf<Int>(1, 2, 3)
val json = Json.encodeToString(map)
assertEquals("""[1,2,3]""", json)

// JSON 配列を Set<T> に変換することも可能
val json = """[1,2,3]"""
val data = Json.decodeFromString<Set<Int>>(json)
assertEquals(setOf(1, 2, 3), data)

// 重複した要素を持つ場合も正しく変換できる
val json = """[1,2,1]"""
val data = Json.decodeFromString<Set<Int>>(json)
assertEquals(setOf(1, 2), data)
assertEquals(2, data.size)
```

```kotlin
@Serializable
data class Data(var id: Int)

// Set<T> の T が独自の data class でも正しく変換される
val json = """[{"id":1}]"""
val data = Json.decodeFromString<Set<Data>>(json)
assertEquals(setOf<Data>(Data(1)), data)
```

```kotlin
enum class Attitude { A, B, C }

@Serializable
data class Data(val id: Attitude)

// 列挙型も正しく変換される
val data = Data(Attitude.A)
val json = Json.encodeToString(data)
assertEquals("""{"id":"A"}""", json)
```

## まとめ

data class のオブジェクトにデフォルト値と同じ値が設定されている場合に出力されないという仕様を除いて、おおよそ想定通りに動いてくれる。出力されない仕様も `{encodeDefaults = true}` を付けることで対応可能。

このようなライブラリを Kotlin 公式が提供してくれているのがとても良い。