再利用可能なJSONレスポンスラッパーにKotlinジェネリクスを使用する
JSON APIレスポンスのラッピングのためにジェネリックなKotlin data classを作成する方法を学びます。型パラメータ、reified型、ジェネリック構造のシリアライゼーションを解説します。
Advanced Patterns
詳細な説明
JSONのためのジェネリックKotlin Data Class
複数のAPIエンドポイントが同じエンベロープ構造(ステータス、データ、ページネーション)を返すがdataペイロードが異なる場合、Kotlinジェネリクスでラッパーを一度定義して再利用できます。
典型的なAPIエンベロープJSON
{
"status": "ok",
"data": {
"users": [
{ "id": 1, "name": "Alice" }
]
},
"pagination": {
"page": 1,
"totalPages": 10
}
}
ジェネリックラッパー
@Serializable
data class ApiResponse<T>(
val status: String,
val data: T,
val pagination: Pagination? = null
)
@Serializable
data class Pagination(
val page: Int,
val totalPages: Int
)
異なるペイロードでの使用
// ユーザーリストエンドポイント用
val userResponse: ApiResponse<UserListData> =
Json.decodeFromString(jsonString)
// 注文詳細エンドポイント用
val orderResponse: ApiResponse<OrderData> =
Json.decodeFromString(jsonString)
Reified型によるインラインパース
inline fun <reified T> parseResponse(json: String): ApiResponse<T> =
Json.decodeFromString(json)
val users = parseResponse<UserListData>(jsonString)
エラーエンベロープ
@Serializable
data class ApiResponse<T>(
val status: String,
val data: T? = null,
val error: ApiError? = null,
val pagination: Pagination? = null
)
@Serializable
data class ApiError(
val code: String,
val message: String
)
nullableなdataとerrorにより、同じ型で成功と失敗の両方を処理します:
val response = parseResponse<UserListData>(jsonString)
if (response.error != null) {
handleError(response.error)
} else {
displayUsers(response.data!!)
}
Sealed Resultの代替
sealed class ApiResult<out T> {
data class Success<T>(val data: T, val pagination: Pagination?) : ApiResult<T>()
data class Error(val code: String, val message: String) : ApiResult<Nothing>()
}
このアプローチはKotlinの型システムを使用して、エラーレスポンスでdataにアクセスすることを不可能にし、より強力なコンパイル時保証を提供します。
ユースケース
AndroidやKotlin MultiplatformのAPIクライアントライブラリを構築する際、同じレスポンスエンベロープを共有する数十のエンドポイントに遭遇します。ジェネリックラッパー型はボイラープレートを排除し、すべてのエンドポイントで一貫したエラー処理を保証します。