kotlinx.serialization — JetBrainsによるマルチプラットフォームライブラリで、リフレクションを使わずにKotlinオブジェクトをJSON、ProtoBuf、CBORなどの形式に変換します。GsonやMoshiとは異なり、@Serializableアノテーションを使用してコンパイル時にシリアライザコードを生成し、高いパフォーマンスと型安全性を実現します。GitHub Kotlin/kotlinx.serializationによると、このライブラリはKotlin/JVM、Kotlin/Native、Kotlin/JS、Kotlin/Wasmをサポートしています。
重要ポイント
kotlinx.serializationは、JetBrainsが公式Kotlinエコシステムの一部として開発した、Kotlin向けの組み込みシリアライゼーションライブラリです。サードパーティ製ソリューション(Gson、Moshi、Jackson)との主な違いは、実行時にリフレクションを使用しないことです。代わりに、シリアライザコードはKotlin Symbol Processing(KSP)またはKotlinコンパイラプラグインを使用してコンパイル時に生成されます。これにより、Gsonと比較して最大3–5倍のパフォーマンス向上と型安全性が保証されます。
このライブラリは公式に4つの形式をサポートしています: JSON(kotlinx-serialization-jsonモジュール経由)、ProtoBuf(kotlinx-serialization-protobuf)、CBOR(kotlinx-serialization-cbor)、HOCON(kotlinx-serialization-hocon)。各形式はbuild.gradle.ktsの個別の依存関係として追加されるため、プロジェクトに不要なライブラリを取り込むことはありません。各形式には独自の設定パラメータセットがあります。
マルチプラットフォームは、このライブラリの重要な機能です。@Serializableが付いた同じクラスが、JVM(Android、バックエンド)、Native(iOS)、JS(Web、React)、Wasm(WebAssembly)のすべてのターゲットで動作します。開発者はプラットフォームごとに異なるシリアライゼーションを実装する必要はありません — コードは統一されています。これは、共有コードがAndroidとiOSで使用されるKotlin Multiplatform Mobile(KMM)プロジェクトで特に価値があります。
kotlinx.serializationのコード生成は3段階で行われます。第1段階では、Kotlinコンパイラがクラス上の@Serializableアノテーションを検出し、Kotlin Symbol Processing(KSP)プラグインに渡します。第2段階では、KSPがKSerializerインターフェースを実装するシリアライザオブジェクトを生成します。第3段階では、生成されたコードがプロジェクトのソースコードと共にコンパイルされます。その結果、これらの段階はいずれもアプリケーションの実行時には実行されません。
生成されたシリアライザは、リフレクションを使わずに、ゲッターとセッターを介してクラスのフィールドと直接やり取りします。つまり、private修飾子が付いたフィールドも、@Serializableがマークされていればシリアライズされます。このアプローチのパフォーマンスは手動シリアライゼーションに近く、単純なクラス(5–10フィールド)では10–50マイクロ秒、複雑なオブジェクトグラフでは1000オブジェクトあたり最大200マイクロ秒です。
AndroidまたはKotlin/JVMプロジェクトにライブラリを追加するには、build.gradle.ktsにプラグインと依存関係を追加します。org.jetbrains.kotlin.plugin.serializationプラグイン(Kotlinのバージョンに一致)がコード生成を有効にします。kotlinx-serialization-jsonライブラリは、Kotlinのバージョンとは独立したバージョンで依存関係セクションに追加されます。
// build.gradle.kts — kotlinx.serializationの追加
plugins {
val kotlinVersion = "2.1.0"
kotlin("jvm") version kotlinVersion
kotlin("plugin.serialization") version kotlinVersion
}
dependencies {
// メインシリアライゼーションモジュール
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
// 追加形式
implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-cbor:1.7.3")
}
JSONはkotlinx.serializationで最も人気のある形式です。オブジェクトをシリアライズするには、data classに@Serializableアノテーションを付け、Json.encodeToString()を呼び出します。デシリアライズするには、型を指定してJson.decodeFromString()を呼び出します。ライブラリはnullフィールド、リスト、ネストされたオブジェクト、enumsを自動的に処理します。特に指定がない限り、クラスのすべてのフィールドはデフォルトで必須です。
JSONの設定はJson {} builderを介して行います。ignoreUnknownKeys = trueを渡すとデシリアライズ時に不明なフィールドをスキップし、prettyPrint = trueで整形出力、coerceInputValues = trueで無効な値をデフォルト値に変換できます。encodeDefaults(デフォルト値を持つフィールドをシリアライズ)やclassDiscriminator(ポリモーフィックシリアライゼーションのフィールド名)も利用可能です。
// JSONシリアライゼーションとデシリアライゼーションの例
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
@Serializable
data class Project(
val name: String,
val stars: Int,
val isActive: Boolean = true,
val languages: List<String> = emptyList()
)
fun main() {
val project = Project(
name = "kotlinx.serialization",
stars = 7200,
languages = listOf("Kotlin", "Java")
)
// prettyPrintを使用したJSONシリアライゼーション
val json = Json { prettyPrint = true }
val jsonString = json.encodeToString(project)
println(jsonString)
/*
{
"name": "kotlinx.serialization",
"stars": 7200,
"isActive": true,
"languages": ["Kotlin", "Java"]
}
*/
// JSONからのデシリアライゼーション
val decoded = json.decodeFromString<Project>(jsonString)
println(decoded.name) // kotlinx.serialization
}
この例は、シリアライゼーションとデシリアライゼーションの基本サイクルを示しています。@Serializableアノテーションが付いたdata class Projectは、自動的にencodeToStringとdecodeFromStringを取得します。isActiveフィールドのデフォルト値はtrueで、JSONにこのフィールドがない場合はデフォルト値が使用されます。ignoreUnknownKeys = trueなしでJSONに不明なフィールドが来ると、SerializationExceptionがスローされます。
Sealed classはkotlinx.serializationの最も強力なユースケースの1つです。このライブラリは、追加設定なしでsealed class階層のポリモーフィックシリアライゼーションをサポートします。sealed classとそのすべてのサブクラスに@Serializableを付けるだけです。シリアライゼーション時に“type”フィールドが追加され(classDiscriminatorで設定可能)、デシリアライゼーション時に具体的な型を決定します。
// Sealed classのポリモーフィックシリアライゼーション
@Serializable
sealed class Response
@Serializable
data class Success(val data: String) : Response()
@Serializable
data class Error(val code: Int, val message: String) : Response()
fun main() {
val json = Json { classDiscriminator = "result_type" }
val responses: List<Response> = listOf(
Success(data = "Data loaded"),
Error(code = 404, message = "Not found")
)
val jsonString = json.encodeToString(responses)
println(jsonString)
/*
[
{"result_type":"Success","data":"Data loaded"},
{"result_type":"Error","code":404,"message":"Not found"}
]
*/
val decoded = json.decodeFromString<List<Response>>(jsonString)
when (val first = decoded[0]) {
is Success -> println("Success: ${first.data}")
is Error -> println("Error: ${first.code}")
}
}
Sealed classのポリモーフィックシリアライゼーションは、サーバーが異なる応答タイプを返すAPIクライアントで特に有用です。kotlinx.serializationがない場合は、判別フィールドに対するwhen文を使った手動デシリアライザを書く必要がありました。このライブラリを使えば、1つのアノテーションで完了します。classDiscriminatorを使用すると、マーカーフィールド(デフォルトは“type”)をサーバーが期待する任意の値に変更できます。
このライブラリは、シリアライゼーションを微調整するためのアノテーションセットを提供します。主要なものはクラス用の@Serializableです。追加のアノテーション: JSONでフィールド名を設定する@SerialName(Kotlin名と異なる場合)、シリアライゼーションからフィールドを除外する@Transient、JSONに必須フィールドを指定する@Required、デフォルト値でもフィールドを強制的にシリアライズする@EncodeDefaultです。
| アノテーション | 目的 | 例 |
|---|---|---|
| @Serializable | クラスのシリアライザ生成を有効にする | @Serializable data class User |
| @SerialName | 形式内のフィールドの代替名を設定する | @SerialName(“user_name”) val name: String |
| @Transient | シリアライゼーションからフィールドを除外する | @Transient val cache: MutableMap |
| @Required | デシリアライズ時にJSONでフィールドが必須 | @Required val id: String |
| @EncodeDefault | デフォルト値でもフィールドをシリアライズ | @EncodeDefault val type: Type = Type.A |
| @Serializer | カスタムシリアライザをクラスにバインド | @Serializer(forClass = Date::class) |
@SerialNameアノテーションは、フィールド名がsnake_caseでKotlinスタイルがcamelCaseのAPIを扱う際に重要です。例えば、サーバーが“user_id”を送信する一方、KotlinコードではuserIdを使用する場合、@SerialName(“user_id”)で追加のマッパーなしに問題を解決します。@Transientは、サーバーに送信すべきでないフィールド(一時的な計算値やキャッシュなど)に便利です。
デフォルトでは、kotlinx.serializationのすべてのフィールドは必須です。フィールドがJSONに存在しない可能性がある場合は、nullable(String?)にするか、デフォルト値を設定します(val name: String = “”)。ただし、Kotlinではnon-nullableだが、APIのバージョニングによりJSONに存在しない可能性があるフィールドもあります。この場合、@RequiredはフィールドがないときにSerializationExceptionをスローし、デフォルト値はエラーなしで埋めます。
KSerializerは、kotlinx.serializationのすべてのシリアライザが実装するインターフェースです。標準のコード生成が適さない場合(Date、Bitmap、特定のバイナリ形式などを扱う場合)、独自のシリアライザを作成できます。これには、serialize()とdeserialize()メソッドを実装し、形式スキーマの構造記述であるディスクリプタを提供します。
カスタムシリアライザは2つの方法で接続します: @Serializable(with = MySerializer::class)アノテーションで特定のクラスにバインドするか、Json { serializersModule = ... }でグローバルにすべてのインスタンスにバインドします。2番目の方法は、組み込み型(Date、UUID)に推奨され、すべてのフィールドにアノテーションを書く必要がありません。
// java.util.Dateのカスタムシリアライザ
import kotlinx.serialization.KSerializer
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
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
object DateSerializer : KSerializer<Date> {
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US)
override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("Date", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Date) {
encoder.encodeString(dateFormat.format(value))
}
override fun deserialize(decoder: Decoder): Date {
return dateFormat.parse(decoder.decodeString())
}
}
// カスタムシリアライザの使用
@Serializable
data class Event(
val title: String,
@Serializable(with = DateSerializer::class)
val date: Date
)
fun main() {
val json = Json { prettyPrint = true }
val event = Event("Release", Date())
println(json.encodeToString(event))
}
この例では、DateSerializerがjava.util.DateをISO 8601文字列に変換します。カスタムシリアライザがないと、kotlinx.serializationはDateを扱えません — これはKotlin標準ライブラリに含まれない型です。特定のフィールドに@Serializable(with = DateSerializer::class)を指定すると、そのフィールドのみにシリアライザが適用されます。すべてのDateをグローバル登録するには、Json { serializersModule = SerializersModule { contextual(DateSerializer) } }を使用します。
kotlinx.serializationはJSONだけに限定されません。このライブラリは4つの組み込み形式をサポートしており、それぞれ独自のモジュールと設定があります。JSON(kotlinx-serialization-json)は汎用的で人間が読みやすく、REST APIに適しています。ProtoBuf(kotlinx-serialization-protobuf)はバイナリでコンパクト、必須スキーマあり、高負荷マイクロサービス向けです。CBOR(kotlinx-serialization-cbor)はJSONのバイナリ版で、帯域幅が限られたIoTやモバイル端末に便利です。HOCON(kotlinx-serialization-hocon)はTypeSafe Config互換の設定形式です。
| 形式 | モジュール | タイプ | スキーマ | 典型的な用途 |
|---|---|---|---|---|
| JSON | kotlinx-serialization-json | テキスト | オプション | REST API、データストレージ |
| ProtoBuf | kotlinx-serialization-protobuf | バイナリ | 必須 (.proto) | マイクロサービス、gRPC |
| CBOR | kotlinx-serialization-cbor | バイナリ | オプション | IoT、モバイル端末 |
| HOCON | kotlinx-serialization-hocon | テキスト | オプション | 設定ファイル |
ProtoBufは.protoファイルでのスキーマ定義が必要ですが、kotlinx-serialization-protobufは.protoなしで@Serializableから直接Kotlinクラスを生成します。これにより開発が簡素化されます: data classにアノテーションを付けてProtoBuf.encodeToByteArray()を使用するだけです。CBORは、NFCやBLEを介してコンパクトなバイナリデータを転送する必要があるAndroidフレームワークで特に重要です。同じデータセットでCBORメッセージはJSONより平均20–30%小さくなります。
モバイルアプリのREST APIには、JSONが最適です — 追加ツールなしでデバッグでき、ログで読み取れ、どのバックエンドとも互換性があります。マイクロサービス間で大量のデータ(数百MB)を転送するアプリでは、ProtoBufはバイナリエンコーディングにより最大5倍の速度向上を提供します。ファイルに設定を保存するには、HOCONまたはJSONを使用します。厳格なトラフィック制限があるデバイス(IoTセンサー)にはCBORを使用してください。
最初の間違いは、デシリアライズ時に不明なキーを無視しないことです。サーバーが新しいフィールドを追加し、ignoreUnknownKeys = falseの場合、アプリケーションはSerializationExceptionでクラッシュします。このフラグはデフォルトでオフです。解決策: APIの変更に耐えられるよう、プロダクションコードでは常にJson { ignoreUnknownKeys = true }を設定してください。
2番目の間違いは、data classのinternalまたはprivateフィールドをシリアライズすることです。Kotlinのdata classでは、プライマリコンストラクタのすべてのフィールドがデフォルトでシリアライズされます。フィールドに機密データ(パスワード、トークン)が含まれる場合は、@Transientを付けるかプライマリコンストラクタから外す必要があります。@TransientはJSONからフィールドを完全に除外しますが、コンストラクタ内ではエラーの原因になるため、そのようなフィールドは@Transientを付けてクラス本体で定義することをお勧めします。
3番目の間違いは、sealed classなしのポリモーフィックシリアライゼーションです。sealedの代わりにopen classを使用する場合、kotlinx.serializationはserializersModule内のすべてのサブクラスの明示的な登録を必要とします。コンパイラがすべてのサブクラスを認識しているsealed classとは異なり、open classは任意の拡張を許可するため、ライブラリは自動的にすべてのサブタイプを判別できません。登録はJson { serializersModule = SerializersModule { polymorphic(Base::class) { subclass(Derived::class) } } }を介して行います。
kotlinx.serializationのバージョンはKotlinのバージョンと互換性がある必要があります。JetBrainsは互換性テーブルを公開しています: kotlinx-serialization 1.6.xはKotlin 1.9.xと互換性があり、1.7.xはKotlin 2.0.xおよび2.1.xと互換性があります。バージョンの不一致は “Symbol ‘serializer’ is missing” などの不可解なコンパイルエラーを引き起こします。Maven CentralまたはプロジェクトのGitHubリポジトリで最新バージョンを必ず確認してください。
よくある質問
kotlinx.serializationはKSPを介したコンパイル時コード生成を使用するのに対し、GsonとMoshiは実行時リフレクションを使用します。これによりパフォーマンス面での利点(Gsonの3–5倍高速)と型安全性が得られます。Gsonはアノテーションなしですべてのフィールドをシリアライズするため、データ漏洩の原因になる可能性があります。kotlinx.serializationは明示的な@Serializableアノテーションを必要とするため、より安全です。Moshiもcodegenをサポートしていますが、JVMとAndroidのみです。
はい、kotlinx.serializationはJetBrainsの公式マルチプラットフォームライブラリです。Kotlin/JVM(Android、バックエンド)、Kotlin/Native(iOS)、Kotlin/JS(Web、React)、Kotlin/Wasmで動作します。APIはすべてのプラットフォームで統一されています: @Serializable + Json.encodeToString()はどこでも同じように動作します。iOSに追加の設定は不要です — Kotlin/Nativeがシリアライズコードをネイティブバイナリにコンパイルします。
Nullableフィールド(String?)は、JSONで値がないかnullの場合、nullとしてデシリアライズされます。デフォルト値のないnon-nullableフィールド(String)の場合、JSONにフィールドがないとSerializationExceptionがスローされます。null値をJSONに出力したくない場合は、Json { encodeDefaults = false }と設定します。これにより、デフォルト値と等しいすべてのフィールド(nullable型のnullを含む)が除外されます。
使用する: 名前がKotlin形式と異なる各フィールドに@SerialName(“s snake_case_name”)を付けます。代替として、Kotlin 2.0+ではJson { namingStrategy = JsonNamingStrategy.SnakeCase }が利用可能で、camelCase ↔ snake_caseの自動変換を行います。この設定はすべてのフィールドに一度に適用されます。部分的なカスタマイズが必要な場合は、@SerialNameをグローバル戦略と組み合わせて使用します。
いいえ、Flowとコルーチンは直接シリアライズできません — これらは非同期実行を表すものであり、データではありません。Flowからデータを転送するには、コルーチン内で.toList()を使用してコレクションに収集し、そのコレクションをシリアライズします。同様に、Job、Deferred、Continuationもシリアライズできません。シリアライズするのはdata classのみ — 振る舞いロジックのないデータモデルです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。