Kotlin Sealed Classで多態的なJSONをモデリングする
型によって形状が変わる多態的なJSON構造を表現するためにKotlin sealed classを使用する方法を学びます。ディスクリミネーターフィールドと網羅的パターンマッチングを解説します。
Advanced Patterns
詳細な説明
Sealed Classによる多態的なJSON
JSONフィールドがディスクリミネーター("type"など)に応じて異なる形状のオブジェクトを含む場合、Kotlin sealed classは網羅的パターンマッチング付きの型安全な表現を提供します。
JSONの例
{
"type": "text",
"content": "Hello, world!"
}
{
"type": "image",
"url": "https://example.com/photo.jpg",
"width": 800,
"height": 600
}
生成されるKotlin
@Serializable
sealed class Message {
abstract val type: String
@Serializable
@SerialName("text")
data class Text(
override val type: String = "text",
val content: String
) : Message()
@Serializable
@SerialName("image")
data class Image(
override val type: String = "image",
val url: String,
val width: Int,
val height: Int
) : Message()
}
網羅的なWhen式
fun render(message: Message) = when (message) {
is Message.Text -> renderText(message.content)
is Message.Image -> renderImage(message.url, message.width, message.height)
// サブタイプが処理されていないとコンパイルエラー
}
kotlinx.serializationの多態性
val module = SerializersModule {
polymorphic(Message::class) {
subclass(Message.Text::class)
subclass(Message.Image::class)
}
}
val json = Json {
serializersModule = module
classDiscriminator = "type"
}
Sealed Class vs Enum Classの使い分け
| 特徴 | Enum | Sealed Class |
|---|---|---|
| 固定の値セット | はい | はい |
| 各バリアントが異なるデータを保持 | いいえ | はい |
| パターンマッチング | はい | はい |
| 標準でシリアライズ可能 | はい | 設定が必要 |
バリアントが単なるラベルの場合はenumを使用します。各バリアントが異なる構造を持つ場合はsealed classを使用します。
Sealed Interface (Kotlin 1.5+)
sealed interface Event {
data class Click(val x: Int, val y: Int) : Event
data class KeyPress(val key: String) : Event
data object Logout : Event
}
Sealed interfaceはクラスが複数のsealed階層を実装できるため、sealed classよりも柔軟性があります。
ユースケース
チャットアプリ、通知システム、イベント駆動アーキテクチャは形状が変わるJSONペイロードを受信します。Sealed classはすべてのバリアントがコンパイル時に処理されることを保証し、Android UIレンダリングやサーバーイベント処理でのケース漏れバグを排除します。