Kotlin: Kotlinのシリアライゼーション
最終更新:2026-08-26
kotlinx.serialization は、コンパイラプラグインを介してコンパイル時にシリアライザを生成します。CharlieによるOrder ↔ JSON変換は、リフレクションを一切使用せず、型安全であり、Jackson よりも高速です。 @SerialName では 1 行でフィールドのマッピングを処理でき、Json { ignoreUnknownKeys = true } では 1 行でフォールトトレランスを設定できます。
1. 学習内容
@Serializableコンパイル時にシリアライザを生成するコンパイラプラグイン- JSONのエンコード/デコード:
encodeToString/decodeFromString<T> - 任意のフィールドとデフォルト値:
@SerialName/@Required - マルチフォーマット対応:JSON / ProtoBuf / CBOR / HOCON
- Charlieの実践:Order ↔ JSON + フィールドマッピング + フォールトトレラントな設定
2. ある開発者の実体験
(1) 課題:リフレクションに基づくシリアライゼーションによるランタイムのパフォーマンス急激な低下
Bobは、Orderオブジェクトをシリアル化するためにJacksonのreflectionモードを使用しました。リファクタリングの過程で、orderIdがidに名前変更されたため、JSONのデシリアル化が(フィールド名の不一致により)静かに失敗し、500件の注文レコードが失われてしまいました。
(2) コンパイル時のシリアライゼーションの解決策
KOTLIN
// Jackson: reflection-based, errors at runtime
@JsonAlias("order_id") // Easy to forget
data class Order(val orderId: String, ...)
// kotlinx.serialization: compile-time, errors at compile time
@Serializable
data class Order(
@SerialName("order_id") val id: String, // Compiler verifies!
val total: Double
)
コンパイル時のシリアライザ生成 — フィールド名の変更は、実行時のデータ損失ではなく、コンパイルエラーとして扱われます。
3. @Serializable とコンパイラプラグイン
(1) Gradleの設定
KOTLIN
// build.gradle.kts
plugins {
kotlin("jvm") version "1.9.22"
kotlin("plugin.serialization") version "1.9.22" // Serialization compiler plugin
}
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
(2) 基本的なシリアライズ
KOTLIN
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@Serializable
data class Order(val id: String, val total: Double, val status: String)
// Serialize: Object -> JSON String
val order = Order("ORD-001", 299.99, "CONFIRMED")
val json = Json.encodeToString(order)
// {"id":"ORD-001","total":299.99,"status":"CONFIRMED"}
// Deserialize: JSON String -> Object
val decoded = Json.decodeFromString<Order>(json)
// Order(id=ORD-001, total=299.99, status=CONFIRMED)
(3) リフレクションとコンパイル時のシリアライズ
| ディメンション | ジャクソン(リフレクション) | kotlinx.serialization |
|---|---|---|
| 仕組み | 実行時リフレクション | コンパイル時コード生成 |
| 安全性 | 実行時エラー | コンパイル時エラー |
| パフォーマンス | 遅い | 速い(リフレクションのオーバーヘッドなし) |
| ProGuard | キープルールが必要 | 不要 |
| マルチプラットフォーム | JVMのみ | JVM / ネイティブ / JS |
4. JSONのエンコードとデコード
(1) JSON設定
KOTLIN
// Default: strict mode
val strictJson = Json // Fails on unknown keys
// Lenient: ignore unknown keys (API versioning)
val lenientJson = Json {
ignoreUnknownKeys = true // Ignore fields not in class
isLenient = true // Accept malformed JSON
encodeDefaults = true // Include fields with default values
prettyPrint = true // Pretty print output
prettyPrintIndent = " " // Indentation
coerceInputValues = true // Use default for null on non-null
}
val json = lenientJson.encodeToString(order)
(2) JSON設定オプション
| オプション | 既定値 | 説明 |
|---|---|---|
ignoreUnknownKeys |
false | クラスに存在しないJSONフィールドを無視する |
isLenient |
false | 寛容な解析(非標準のJSONを受け入れる) |
encodeDefaults |
false | フィールドをデフォルト値でエンコード |
prettyPrint |
false | 書式付き出力 |
coerceInputValues |
false | nullではないフィールドにnullが渡された場合にデフォルト値を使用する |
5. フィールドのマッピングとデフォルト値
(1) @SerialName フィールドのマッピング
KOTLIN
@Serializable
data class Order(
@SerialName("order_id") val id: String, // JSON: order_id
@SerialName("order_total") val total: Double, // JSON: order_total
val status: String = "PENDING" // JSON: status (same name)
)
// JSON: {"order_id":"ORD-001","order_total":299.99,"status":"CONFIRMED"}
(2) オプションのフィールドとデフォルト値
KOTLIN
@Serializable
data class Order(
@SerialName("order_id") val id: String,
@SerialName("order_total") val total: Double,
val status: String = "PENDING", // Optional with default
val customer: String? = null, // Nullable optional
@SerialName("tax_rate") val taxRate: Double = 0.08
)
// Missing optional fields use defaults
val json = """{"order_id":"ORD-001","order_total":299.99}"""
val order = Json.decodeFromString<Order>(json)
// Order(id=ORD-001, total=299.99, status=PENDING, customer=null, taxRate=0.08)
(3) @Required による必須項目の指定
KOTLIN
@Serializable
data class Order(
@Required val id: String, // MUST be present in JSON
@Required val total: Double // MUST be present in JSON
)
// Missing 'id' or 'total' -> SerializationException
6. マルチフォーマット対応
KOTLIN
// ProtoBuf
import kotlinx.serialization.protobuf.ProtoBuf
val protoBytes = ProtoBuf.encodeToByteArray(order)
val fromProto = ProtoBuf.decodeFromByteArray<Order>(protoBytes)
// CBOR
import kotlinx.serialization.cbor.Cbor
val cborBytes = Cbor.encodeToByteArray(order)
// HOCON (configuration format)
import kotlinx.serialization.hocon.Hocon
(1) フォーマットの比較
| 形式 | 読みやすさ | サイズ | 速度 | 用途 |
|---|---|---|---|---|
| JSON | 高 | 大 | 中 | API通信 |
| ProtoBuf | 低(バイナリ) | コンパクト | 高速 | 高性能RPC |
| CBOR | 低速(バイナリ) | 中速 | 高速 | IoT/組み込み |
| HOCON | 高 | 中 | 中 | 設定ファイル |
7. シリアライゼーションのエンコード/デコードの流れ
sequenceDiagram
participant Obj as Order Object
participant Ser as Serializer
participant JSON as JSON String
Note over Obj,Ser: Encode
Obj->>Ser: @Serializable properties
Ser->>JSON: encodeToString()
Note over JSON: {"order_id":"ORD-001",...}
Note over Ser,Obj: Decode
JSON->>Ser: decodeFromString<Order>(json)
Ser->>Obj: @Serializable constructor
Note over Obj: Order(id=ORD-001,...)
8. 完全な例:OrderProcessor の JSON シリアライズ
▶ サンプル:OrderProcessorのJSONシリアライズ
KOTLIN
// ============================================
// OrderProcessor - JSON Serialization
// Feature: Order <-> JSON with field mapping
// ============================================
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class Address(
val street: String,
val city: String,
val country: String
)
@Serializable
data class OrderItem(
val sku: String,
val quantity: Int,
@SerialName("unit_price") val unitPrice: Double
) {
val subtotal: Double get() = quantity * unitPrice
}
@Serializable
data class Order(
@SerialName("order_id") val id: String,
@SerialName("order_total") val total: Double,
val status: String = "PENDING",
val customer: String? = null,
val items: List<OrderItem> = emptyList(),
val address: Address? = null,
@SerialName("tax_rate") val taxRate: Double = 0.08,
@SerialName("created_at") val createdAt: String = "2026-01-01T00:00:00Z"
)
val orderJson = Json {
ignoreUnknownKeys = true
encodeDefaults = true
prettyPrint = true
prettyPrintIndent = " "
}
fun main() {
// Create order
val order = Order(
id = "ORD-001",
total = 299.99,
status = "CONFIRMED",
customer = "Alice",
items = listOf(
OrderItem("SKU-WIDGET", 3, 9.99),
OrderItem("SKU-GADGET", 1, 149.99)
),
address = Address("123 Main St", "New York", "US"),
createdAt = "2026-07-13T10:30:00Z"
)
// Serialize: Order -> JSON
println("=== Serialize ===")
val jsonString = orderJson.encodeToString(order)
println(jsonString)
// Deserialize: JSON -> Order
println("\n=== Deserialize ===")
val decoded = orderJson.decodeFromString<Order>(jsonString)
println("Order: ${decoded.id}, Total: \$${decoded.total} USD")
println("Items: ${decoded.items.map { "${it.sku} x${it.quantity}" }}")
// Handle unknown keys (API versioning)
println("\n=== API Versioning ===")
val jsonWithExtraFields = """
{
"order_id": "ORD-002",
"order_total": 1500.00,
"status": "SHIPPED",
"unknown_field": "this is fine",
"new_api_version": 2
}
""".trimIndent()
val decodedWithExtra = orderJson.decodeFromString<Order>(jsonWithExtraFields)
println("Decoded with extra fields: ${decodedWithExtra.id}")
// Minimal JSON (required fields only)
println("\n=== Minimal JSON ===")
val minimalJson = """{"order_id":"ORD-003","order_total":45.50}"""
val minimal = orderJson.decodeFromString<Order>(minimalJson)
println("Minimal: ${minimal.id}, Status: ${minimal.status}, Customer: ${minimal.customer}")
}
出力:
TEXT
📖 参照専用
=== Serialize ===
{
"order_id": "ORD-001",
"order_total": 299.99,
"status": "CONFIRMED",
"customer": "Alice",
"items": [
{
"sku": "SKU-WIDGET",
"quantity": 3,
"unit_price": 9.99
},
{
"sku": "SKU-GADGET",
"quantity": 1,
"unit_price": 149.99
}
],
"address": {
"street": "123 Main St",
"city": "New York",
"country": "US"
},
"tax_rate": 0.08,
"created_at": "2026-07-13T10:30:00Z"
}
=== Deserialize ===
Order: ORD-001, Total: $299.99 USD
Items: [SKU-WIDGET x3, SKU-GADGET x1]
=== API Versioning ===
Decoded with extra fields: ORD-002
=== Minimal JSON ===
Minimal: ORD-003, Status: PENDING, Customer: null
❓ よくある質問
Q kotlinx.serialization を Jackson と併用することはできますか?
A はい、可能ですが、推奨されません。2つのシリアライズ方式は異なります。移行中は、
@JsonAlias をブリッジとして使用できますが、新規プロジェクトでは、最初から kotlinx.serialization を使用してください。Q シールクラスはシリアライズできますか?
A はい。
@Serializable シールクラスには自動的に型識別子 ("type":"SubClassName") が含まれており、デシリアライズ時に適切なサブクラスが選択されます。Q シリアライズロジックをカスタマイズするにはどうすればよいですか?
A
@Serializable(with = CustomSerializer::class) を使用してカスタムシリアライザを指定するか、KSerializer<T> インターフェースを実装して、エンコード/デコードを手動で制御してください。Q シリアライゼーションはジェネリックに対応していますか?
A はい。ただし、ジェネリッククラスに
@Serializable アノテーションを付ける必要があります。decodeFromString では、型パラメータを明示的に指定する必要があります。Q なぜコンパイラプラグインが必要なのですか?
A Kotlinのコンパイラプラグインは、コンパイル時に
@Serializableクラスのシリアライザコードを自動生成し、実行時のリフレクションを回避します。これが、そのパフォーマンスと安全性の基盤となっています。Q @SerialName と @JsonProperty の違いは何ですか?
A 機能的には同じ(JSON フィールド名のマッピング)ですが、@SerialName は kotlinx.serialization のアノテーションであり、コンパイル時に処理されます。一方、 @JsonPropertyは、Jacksonの注釈であり、リフレクションを介して実行時に処理されます。
📖 まとめ
@Serializable+ コンパイラプラグインがシリアライザを自動生成 — リフレクションを一切使用しないJson.encodeToString/Json.decodeFromString<T>(符号化および復号用)@SerialNameは、Kotlinのプロパティ名とは切り離された形で、JSONのフィールド名をマッピングします- デフォルト値 = 任意のフィールド;
@Requiredを指定すると、そのフィールドが必須となる ignoreUnknownKeys = trueは API バージョンの互換性を有効にします- マルチフォーマット対応:JSON / ProtoBuf / CBOR / HOCON
📝 練習問題
- 初心者 (⭐):
@SerializableをOrderに追加し、JSON にシリアライズしてから、再びデシリアライズしてください。ヒント:@Serializable data class Order(...) - 中級 (⭐⭐):
@SerialNameを使用して注文フィールド名 (order_id、order_total) をマッピングし、API の将来的な変更に備えてJson { ignoreUnknownKeys = true }を設定します。ヒント:第5節を参照してください。 - 課題 (⭐⭐⭐):
Orderアイテムをフラットな JSON 形式に展開する(アイテムをitem_1_sku、item_1_qty形式に展開する)カスタムシリアライザを実装してください。 ヒント:KSerializer<Order>を実装してください。