Kotlin: Kotlinのシリアライゼーション

最終更新:2026-08-26

kotlinx.serialization は、コンパイラプラグインを介してコンパイル時にシリアライザを生成します。CharlieによるOrder ↔ JSON変換は、リフレクションを一切使用せず、型安全であり、Jackson よりも高速です。 @SerialName では 1 行でフィールドのマッピングを処理でき、Json { ignoreUnknownKeys = true } では 1 行でフォールトトレランスを設定できます。

1. 学習内容


2. ある開発者の実体験

(1) 課題:リフレクションに基づくシリアライゼーションによるランタイムのパフォーマンス急激な低下

Bobは、Orderオブジェクトをシリアル化するためにJacksonのreflectionモードを使用しました。リファクタリングの過程で、orderIdidに名前変更されたため、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. シリアライゼーションのエンコード/デコードの流れ

100%
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の注釈であり、リフレクションを介して実行時に処理されます。

📖 まとめ


📝 練習問題

  1. 初心者 (⭐): @SerializableOrder に追加し、JSON にシリアライズしてから、再びデシリアライズしてください。ヒント: @Serializable data class Order(...)
  2. 中級 (⭐⭐): @SerialName を使用して注文フィールド名 (order_idorder_total) をマッピングし、API の将来的な変更に備えて Json { ignoreUnknownKeys = true } を設定します。ヒント:第5節を参照してください。
  3. 課題 (⭐⭐⭐): Order アイテムをフラットな JSON 形式に展開する(アイテムを item_1_skuitem_1_qty 形式に展開する)カスタムシリアライザを実装してください。 ヒント:KSerializer<Order> を実装してください。

← 前へ | 次へ →

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%