KotlinでJSONを扱うときによく使われるのが、kotlinx.serialization です。
これは、KotlinオブジェクトをJSON文字列に変換したり、JSON文字列をKotlinオブジェクトに戻したりするための公式ライブラリです。
Kotlin Serializationの大きな特徴は、@Serializable を付けたクラスに対して、コンパイラがシリアライズ用のコードを自動生成してくれることです。
そのため、Kotlinの型システムと相性がよく、型安全に扱いやすいのが強みです。
さらに、Kotlin Multiplatformにも対応しており、JSON以外にProtobuf、CBOR、HOCON、Propertiesなどの形式も扱えます。
ただし、実務で最もよく使われるのはJSONです。まずはJSONを中心に理解するのが分かりやすいです。
Kotlin Serializationでできること
Kotlin Serializationでは、主に次のような処理ができます。
- KotlinのデータクラスをJSONに変換する
- JSON文字列をKotlinのデータクラスに変換する
- ネストしたオブジェクトやリストをそのまま扱う
- JSONキー名とKotlinプロパティ名が違う場合に対応する
- APIレスポンスの余分なキーを無視する
sealed classを使った多態的なデータ構造を扱う- 必要に応じて独自のシリアライズ処理を定義する
つまり、単純なJSONの読み書きだけでなく、実際のAPI通信で必要になることの多くをカバーできます。
まずはセットアップ
Kotlin Serializationを使うには、Gradleのプラグインと依存関係を追加する必要があります。
build.gradle.kts の例
plugins {
kotlin("jvm") version "2.3.20"
kotlin("plugin.serialization") version "2.3.20"
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
}
ここで重要なのは次の2つです。
kotlin("plugin.serialization")を追加すること- JSONを扱うために
kotlinx-serialization-jsonを追加すること
この2つがそろっていないと、@Serializable を付けても正しく動かないことがあります。
最小構成の使い方
最初に覚えるべき基本はとてもシンプルです。
クラスに @Serializable を付ける
import kotlinx.serialization.Serializable
@Serializable
data class User(
val id: Int,
val name: String
)
KotlinオブジェクトをJSONに変換する
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
fun main() {
val user = User(1, "Taro")
val json = Json.encodeToString(user)
println(json)
}
出力例
{"id":1,"name":"Taro"}
JSONからKotlinオブジェクトに戻す
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
fun main() {
val json = """{"id":1,"name":"Taro"}"""
val user = Json.decodeFromString<User>(json)
println(user)
}
出力例
User(id=1, name=Taro)
この流れがKotlin Serializationの基本です。
実務では Json { ... } を設定して使う
実際の開発では、Json をそのまま使うより、設定を加えて使うことが多いです。
val json = Json {
prettyPrint = true
ignoreUnknownKeys = true
encodeDefaults = false
explicitNulls = true
}
この設定はよく使います。
prettyPrint = true
JSONを整形して見やすく出力します。
ログ確認やデバッグ時に便利です。
ignoreUnknownKeys = true
JSON側に、Kotlinのクラスで定義していない項目が含まれていても無視します。
外部APIと連携するときに非常に重要です。
encodeDefaults = false
デフォルト値と同じ値を持つプロパティを、JSONに出力しない設定です。
これがデフォルトの挙動です。
explicitNulls = true
null を明示的にJSONへ出力する設定です。
これもデフォルトでは true です。
デフォルト値の扱い
Kotlin Serializationでは、プロパティがデフォルト値と同じ場合、JSON出力から省略されることがあります。
@Serializable
data class Profile(
val name: String,
val age: Int = 20
)
val profile = Profile("Taro")
println(Json.encodeToString(profile))
この場合、age は 20 なので、省略される可能性があります。
出力例
{"name":"Taro"}
すべての値を必ず出力したい場合は、encodeDefaults = true を使います。
val json = Json {
encodeDefaults = true
}
これでデフォルト値も含めて出力されます。
null の扱い
nullableな値は普通に扱えます。
@Serializable
data class Product(
val name: String,
val description: String? = null
)
ただし、null をJSONにどう出力するかは explicitNulls の設定に影響されます。
explicitNulls = true
null を明示的に出力します。
{"name":"Book","description":null}
explicitNulls = false
null の項目そのものを省略します。
{"name":"Book"}
APIによっては、null を送るのか、項目ごと省略するのかで意味が変わることがあります。
この違いはかなり重要です。
JSONキー名が違う場合は @SerialName
APIのJSONキー名と、Kotlinのプロパティ名が一致しないことはよくあります。
その場合は @SerialName を使います。
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class Article(
@SerialName("article_id")
val id: Int,
@SerialName("article_title")
val title: String
)
このように書くと、JSONの article_id がKotlinの id に対応し、article_title が title に対応します。
これはAPI連携では非常によく使う機能です。
一括で命名規則を変えたい場合
個別に @SerialName を付けるだけでなく、全体の命名規則をまとめて変換したいケースもあります。
たとえば、Kotlin側では camelCase、JSON側では snake_case を使いたい場合です。
このようなケースでは namingStrategy を使う方法もあります。
ただし、入門段階ではまず @SerialName を覚えるほうが分かりやすいです。
考え方としては次のようになります。
- 個別に対応したいなら
@SerialName - 全体の規則を統一したいなら
namingStrategy
リストやMapもそのまま扱える
Kotlin Serializationは、データクラスだけでなく、ListやMapのような標準コレクションも扱えます。
@Serializable
data class User(
val id: Int,
val name: String
)
fun main() {
val users = listOf(
User(1, "Taro"),
User(2, "Hanako")
)
val json = Json.encodeToString(users)
println(json)
val decoded = Json.decodeFromString<List<User>>(json)
println(decoded)
}
JSON配列とKotlinの List をそのまま相互変換できるので、APIレスポンスを扱いやすいです。
ネストしたオブジェクトも扱える
データクラスの中に別のデータクラスがある構造も普通に扱えます。
@Serializable
data class Address(
val city: String,
val zipCode: String
)
@Serializable
data class Customer(
val name: String,
val address: Address
)
val customer = Customer(
"Taro",
Address("Kyoto", "600-0000")
)
val json = Json.encodeToString(customer)
println(json)
このように、クラスの中に別の @Serializable クラスが入っていても問題ありません。
APIレスポンスでは ignoreUnknownKeys が特に重要
外部APIを使うと、サーバー側の仕様変更でJSONに新しいフィールドが増えることがあります。
たとえば、次のようなJSONが返ってきたとします。
{"id":1,"name":"Taro","extra":"value"}
Kotlin側のクラスがこうだった場合:
@Serializable
data class User(
val id: Int,
val name: String
)
そのままだと、extra を知らないためエラーになることがあります。
これを防ぐのが ignoreUnknownKeys = true です。
val json = Json {
ignoreUnknownKeys = true
}
APIクライアントを書くときは、かなりの頻度で使う設定です。
JsonElement で柔軟に扱う方法
JSONの構造が固定されていないときや、一部だけ動的に処理したいときは JsonElement が便利です。
import kotlinx.serialization.json.*
fun main() {
val element = Json.parseToJsonElement(
"""{"name":"Taro","age":30}"""
)
val obj = element.jsonObject
println(obj["name"]?.jsonPrimitive?.content)
}
JsonElement を使うと、いったんJSONをツリー構造として受け取り、必要な部分だけ手動で見られます。
これは次のような場面で役立ちます。
- APIレスポンスの構造が不安定
- JSONの一部だけ見たい
- 条件によって読み方を変えたい
- いきなり型付きクラスに落とし込めない
カスタムシリアライザ
標準機能だけで十分なことも多いですが、特殊な形式を扱いたいときはカスタムシリアライザを作れます。
たとえば、独自のID型をJSONでは文字列として扱いたい場合です。
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object UserIdSerializer : KSerializer<UserId> {
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("UserId", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: UserId) {
encoder.encodeString(value.value)
}
override fun deserialize(decoder: Decoder): UserId {
return UserId(decoder.decodeString())
}
}
@Serializable(with = UserIdSerializer::class)
data class UserId(val value: String)
@Serializable
data class User(
val id: UserId,
val name: String
)
このようにしておくと、Kotlin側では UserId という独自型で安全に扱いながら、JSON上ではただの文字列として扱えます。
sealed class と polymorphism
少し進んだ話になりますが、sealed class を使った多態的なデータ構造も扱えます。
import kotlinx.serialization.Serializable
@Serializable
sealed class Payment
@Serializable
data class CreditCard(
val number: String
) : Payment()
@Serializable
data class BankTransfer(
val account: String
) : Payment()
このような場合、JSONに変換するときには「どのサブクラスなのか」を識別するための情報が必要になります。
この識別用キーが classDiscriminator です。
val json = Json {
classDiscriminator = "_type"
}
デフォルトでは "type" が使われますが、API仕様によっては別名に変更したいことがあります。
このあたりは入門者には少し難しい部分ですが、要点は単純です。
- 普通のデータクラスなら特に意識しなくてよい
sealed classや interface をJSON化するときは型情報が必要になる- その型情報のキー名は
classDiscriminatorで調整できる
特殊な浮動小数点値
通常のJSONでは、NaN や Infinity はそのまま扱えません。
ただし、allowSpecialFloatingPointValues = true を有効にすると扱えるようになります。
val json = Json {
allowSpecialFloatingPointValues = true
}
ただし、これは相手側システムも同じ表現を理解できる場合に限って使うのが安全です。
外部APIに送る用途では注意が必要です。
実務でよくある定番の書き方
APIレスポンス用のDTOを作るなら、次のような形がよく使われます。
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
data class UserResponse(
@SerialName("user_id")
val id: Int,
val name: String,
val email: String? = null
)
fun main() {
val json = Json {
ignoreUnknownKeys = true
prettyPrint = true
explicitNulls = true
}
val raw = """
{
"user_id": 1,
"name": "Taro",
"email": "taro@example.com",
"extra_field": "ignored"
}
""".trimIndent()
val user = json.decodeFromString<UserResponse>(raw)
println(user)
val encoded = json.encodeToString(user)
println(encoded)
}
この形なら、API連携でよく必要になるポイントを一通り押さえられます。
- JSONキー名の違いに対応できる
- 余分なキーを無視できる
- nullable項目を扱える
- KotlinオブジェクトとJSONを往復できる
よくあるエラー
Serializer for class ... is not found
かなりよく出るエラーです。
主な原因は次の通りです。
@Serializableを付け忘れているkotlin("plugin.serialization")を設定していない- 独自型に対するSerializerが必要なのに未設定
- Kotlin本体とライブラリのバージョンの組み合わせが不適切
まずは、クラスに @Serializable が付いているか、Gradle設定が正しいかを確認してください。
余分なキーで落ちる
ignoreUnknownKeys = true を設定していない可能性があります。
null の出力が想定と違う
explicitNulls の設定を確認します。
デフォルト値が出力されない
encodeDefaults = true が必要な可能性があります。
type が衝突する
sealed class 周りで classDiscriminator の名前変更が必要なことがあります。
最初に覚えるべきポイント
最初は、次の内容だけ押さえれば十分です。
- クラスに
@Serializableを付ける - GradleにプラグインとJSON依存関係を追加する
Json.encodeToString()でJSON化するJson.decodeFromString()で復元する- API用途なら
ignoreUnknownKeys = trueを覚える - JSONキー名が違うときは
@SerialNameを使う nullとデフォルト値の扱いとしてexplicitNullsとencodeDefaultsを理解する
このあたりまで理解できれば、かなり実用レベルです。
まとめ
Kotlin Serializationは、KotlinでJSONを安全かつ自然に扱うための標準的な方法です。
とくにデータクラスとの相性がよく、API通信や設定ファイルの読み書きなど、さまざまな場面で使えます。
まずは次の流れを覚えるのがおすすめです。
@Serializableを付けるJson.encodeToString()でJSONにするJson.decodeFromString()で戻す- 必要に応じて
ignoreUnknownKeys、@SerialName、explicitNulls、encodeDefaultsを使う
ここまで理解できれば、日常的なJSON処理ではかなり困らなくなります。
以上、KotlinのSerializationの使い方についてでした。
最後までお読みいただき、ありがとうございました。










