Use Kotlin Generics for Reusable JSON Response Wrappers
Learn how to create generic Kotlin data classes for wrapping JSON API responses. Covers type parameters, reified types, and serialization of generic structures.
Detailed Explanation
Generic Kotlin Data Classes for JSON
When multiple API endpoints return the same envelope structure (status, data, pagination) but with different data payloads, Kotlin generics let you define the wrapper once and reuse it.
Typical API Envelope JSON
{
"status": "ok",
"data": {
"users": [
{ "id": 1, "name": "Alice" }
]
},
"pagination": {
"page": 1,
"totalPages": 10
}
}
Generic Wrapper
@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
)
Usage with Different Payloads
// For user list endpoint
val userResponse: ApiResponse<UserListData> =
Json.decodeFromString(jsonString)
// For order detail endpoint
val orderResponse: ApiResponse<OrderData> =
Json.decodeFromString(jsonString)
Reified Type for Inline Parsing
inline fun <reified T> parseResponse(json: String): ApiResponse<T> =
Json.decodeFromString(json)
val users = parseResponse<UserListData>(jsonString)
Error Envelope
@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
)
With nullable data and error, the same type handles both success and failure:
val response = parseResponse<UserListData>(jsonString)
if (response.error != null) {
handleError(response.error)
} else {
displayUsers(response.data!!)
}
Sealed Result Alternative
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>()
}
This approach uses Kotlin's type system to make it impossible to access data on an error response, providing stronger compile-time guarantees.
Use Case
When building API client libraries for Android or Kotlin Multiplatform, you encounter dozens of endpoints that share the same response envelope. A generic wrapper type eliminates boilerplate and ensures consistent error handling across all endpoints.
Try It — JSON to Kotlin
Related Topics
Model a REST API Response in Kotlin Data Classes
Real-World Patterns
Model Polymorphic JSON with Kotlin Sealed Classes
Advanced Patterns
Kotlin Serialization Annotations for JSON Mapping
Serialization
Convert JSON Arrays to Kotlin Lists
Collections
Handle Nullable JSON Fields in Kotlin Data Classes
Basic Data Classes