I am trying to implement a generic HttpClient like this one:
interface HttpClient {
fun <T: Any> get(url: String): T?
}
implemented by a class like this:
class HttpClientImpl @Inject constructor(...) : HttpClient {
override fun <T : Any> get(url: String): T? = execute(url)
private inline fun <reified T: Any> execute(url: String): T? {
val request = Request.Builder().url(url).get().build()
client.newCall(request).execute().use {
return it.body?.parseBodySuccess()
}
}
private inline fun <reified T: Any> ResponseBody?.parseBody(): T? {
val type = objectMapper.typeFactory.constructType(T::class.java)
return this?.let { objectMapper.readValue(it.string(), type) }
}
}
Now, I would like to be able to call such GET method in this way:
data class MyEntity(...)
class MyService @Inject constructor(private val client: HttpClient) {
fun performGet(url: String): MyEntity? = client.get<MyEntity>(url)
}
However this is not allowed and the compiler throws an error referring to the line of code
override fun <T : Any> get(endpoint: String): T? = execute(endpoint)
flagging that : Cannot use 'T' as reified type parameter. Use a class instead.
I have been trying to re-write the line as
override inline fun <reified T : Any> get(endpoint: String): T? = execute(endpoint)
however, despite having to make the other two inline functions "non private" the compiler still won't compile because in this last way of writing the overriding function, it says:
Override by a function with reified type parameter
How can I achieve such generic function?
CodePudding user response:
I ended up doing something like this:
interface HttpClient {
fun <T: Any> get(url: String, type: Class<T>): T?
}
implemented as:
class HttpClientImpl @Inject constructor(...) : HttpClient {
override fun <T : Any> get(url: String, type: Class<T>): T? = execute(url, type)
private fun <T: Any> execute(url: String, type: Class<T>): T? {
val request = Request.Builder().url(url).get().build()
client.newCall(request).execute().use {
return it.body?.parseBody(type)
}
}
private fun <T: Any> ResponseBody?.parseBody(type: Class<T>): T? {
val dataType = objectMapper.typeFactory.constructType(type)
return this?.let { objectMapper.readValue(it.string(), dataType) }
}
}
that I can call in this way:
data class MyEntity(...)
class MyService @Inject constructor(private val client: HttpClient) {
fun performGet(url: String): MyEntity? = client.get(url, MyEntity::class.java)
}
I would have preferred to pass the Type directly as an actual type like
client.get<MyEntity>(url)
rather than passing the Class as a parameter, however, just for now it works...
If anyone can suggest a better way of doing this, please let me know.
Updated
As suggested by Pawel, I have created an extra inline extension function to the HttpClient interface
inline fun <reified T:Any> HttpClient.get (url: String) = get(url, T::class.java)
And I'm now able to call the function the way I wanted.