Kotlin - Override a function with Generic return type - kotlin

I have an interface. I want to make the return type of one of the function in that interface as generic type. Depend on how it is going to override, the return type will determine. When I try below code, I get errors Conflicting overloads: . I am new to Kotlin.
interface Loader<T> {
fun <T> doSomething(inputParams: Map<String, String> = emptyMap()): T?
fun cancelSomething(inputParams: Map<String, String> = emptyMap())
}
class LoaderHandler<MutableMap<String, String>> (private val foo: Foo) : Loader<MutableMap<String, String>> {
override fun doSomething(inputParams: Map<String, String>): MutableMap<String, String>? {
// some code goes here.
return mapOf("x" to "y")
}
override fun cancelSomething (inputParams: Map<String, String>) {
println("cancelSomething")
}
How can I implement the doSomething(...) function with return type of Map.

Delete <T> in
fun <T> doSomething(inputParams: Map<String, String> = emptyMap()): T?
It is not doing what you think it is.
Additionally,
class LoaderHandler<MutableMap<String, String>> (private val foo: Foo) : Loader<MutableMap<String, String>> {
should be
class LoaderHandler(private val foo: Foo) : Loader<MutableMap<String, String>> {

Related

How to write generic functions in Kotlin interfaces's implementations

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?
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.

extension function on List for elements compliant to multiple interfaces

The following code creates an extension function for List that contain elements that are implementing the InterfaceCustomFirebaseData.
fun List<InterfaceCustomFirebaseData>.toFirebaseDate(): MutableMap<String, Any> {
val firebaseData: MutableMap<String, Any> = mutableMapOf()
this.forEach { firebaseData[it.id] = it.toFirebaseData() }
return firebaseData
}
But I need the List elements to be compliant to 2 interfaces (InterfaceCustomFirebaseData and also InterfaceIdentifiable), but this does not work:
fun List<InterfaceCustomFirebaseData, InterfaceIdentifiable>.toFirebaseDate(): MutableMap<String, Any> { ... }
The compiler gives error "One type argument expected for interface List"
How should this be written in Kotlin?
Found the answer myself:
fun <T> List<T>.toFirebaseDate(): MutableMap<String, Any> where T : InterfaceCustomFirebaseData, T : InterfaceIdentifiable {
val firebaseData: MutableMap<String, Any> = mutableMapOf()
this.forEach { firebaseData[it.id] = it.toFirebaseData() }
return firebaseData
}

how to elegantly create a map with only non-null values in Kotlin

How can I rewrite this code without having to resort to a MutableMap and conversion to immutable map?
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
val map = mutableMapOf("key1" to mandatoryValue)
optionalValue?.let { map.put("key2", it) }
return map.toMap()
}
My alternative solution isn't very nice either because it needs an unsafe cast:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mapOf(
"key1" to mandatoryValue,
"key2" to optionalValue
).filterValues { it != null } as Map<String, String>
What I am looking for is something in the lines of:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mapOf(
"key1" to mandatoryValue,
optionalValue?.let { "key2" to it }
)
You can have your own extension function as follows and then use it to filter null values from a Map:
fun <K, V> Map<K, V?>.filterValuesNotNull() =
mapNotNull { (k, v) -> v?.let { k to v } }.toMap()
toMap() does not necessarily create an immutable map. It is only guaranteed to be read-only. The underlying class instance might be a MutableMap (which in the current implementation is true if it has more than one key). Therefore, toMap() in your first block of code is unnecessary. The MutableMap is automatically upcast to Map when you return it since you specified Map as the return type. So, you could have put
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
val map = mutableMapOf("key1" to mandatoryValue)
optionalValue?.let { map.put("key2", it) }
return map
}
or
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mutableMapOf("key1" to mandatoryValue).apply {
if (optionalValue != null) put("key2", optionalValue)
}
To get the syntax you requested in your last example, you could create an overload of mapOf that accepts and filters null values:
fun <K, V> mapOf(vararg pairs: Pair<K, V>?): Map<K, V> =
mapOf(*pairs.filterNotNull().toTypedArray())
There's nothing wrong with using MutableMap - in fact your first solution (without the redundant toMap()) is already pretty elegant. It's simpler and clearer than any immutable answer will be. Immutable operations come at the cost of additional object creations and copies, so unless you need the guarantees of immutability, it's best to use a MutableMap but only expose it via the Map interface, as you are already doing.
If you really wanted to do it immutably, you could do it like this:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mapOf("key1" to mandatoryValue) +
(optionalValue?.let { mapOf("key2" to it) } ?: emptyMap())
Or equivalently if you prefer:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
mapOf("key1" to mandatoryValue) +
if (optionalValue != null) mapOf("key2" to optionalValue) else emptyMap()
Or:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
val mandatoryMap = mapOf("key1" to mandatoryValue)
return optionalValue?.let { mandatoryMap + ("key2" to optionalValue) } ?: mandatoryMap
}
Or:
fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
val mandatoryMap = mapOf("key1" to mandatoryValue)
return if (optionalValue != null) mandatoryMap + ("key2" to optionalValue) else mandatoryMap
}
In kotlin 1.6
buildMap {
put("key1", mandatoryValue)
if (optionalValue != null)
put("key2", optionalValue)
}
This creates a mutableMap under the sheets, but is quite elegant and readable <3

Deserialize generic object using Kotlin Serialization

I am trying to replace Gson library by kotlin serialization to handle JSON serialization/deserialization.
I am facing some issues to deserialize generic objects I have setup a simple example of what I am trying to achieve:
#Serializable
data class ContentMessageDto<T>(
val method: String,
val content: T
)
#Serializable
private data class DummyObjectNonNullProperties(
val value: Int,
#SerialName("aaa") val someProp: String,
val bbb: Boolean,
val ccc: Double
)
interface MyParser {
fun <T> parseContentMessage(
json: String
): ContentMessageDto<T>
}
class MyParserImpl(private val jsonSerialization: Json) : MyParser {
override fun <T> parseContentMessage(json: String): ContentMessageDto<T> {
return jsonSerialization.decodeFromString<ContentMessageDto<T>>(json)
}
}
fun main() {
println("start processing...")
val jsonToParse = """
{
"method":"get",
"content":{
"value":345,
"aaa": "some string",
"bbb": true,
"ccc": 23.4
}
}""".trimIndent()
val parser:MyParser = MyParserImpl(Json)
val result = parser.parseContentMessage<DummyObjectNonNullProperties>(jsonToParse)
println("result -> $result")
}
But when I run the main method, I get the following error:
Exception in thread "main" java.lang.IllegalStateException: Only KClass supported as classifier, got T
at kotlinx.serialization.internal.Platform_commonKt.kclass(Platform.common.kt:102)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:52)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
at kotlinx.serialization.SerializersKt__SerializersKt.builtinSerializerOrNull$SerializersKt__SerializersKt(Serializers.kt:79)
at kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$SerializersKt__SerializersKt(Serializers.kt:69)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:54)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source)
But I am not sure why. Can someone provide me an explanation and if possible some tips on how I can implement this?
It would have worked if you've simply done:
val result = Json.decodeFromString<ContentMessageDto<DummyObjectNonNullProperties>>(jsonToParse)
But with all this wrapping, type information about T was lost. The problem is that you can't simply use reified generics here, cause inline functions can't be non-final.
Possible workarounds:
Define parseContentMessage as extension function so that it could have inline modifier (and T could be reified):
interface MyParser {
val jsonSerialization: Json
}
inline fun<reified T> MyParser.parseContentMessage(json: String): ContentMessageDto<T> {
return jsonSerialization.decodeFromString(json)
}
class MyParserImpl(override val jsonSerialization: Json) : MyParser
//Usage will be the same
Manually pass serializer for T into parseContentMessage:
interface MyParser {
fun <T> parseContentMessage(json: String, contentSerializer: KSerializer<T>): ContentMessageDto<T>
}
class MyParserImpl(private val jsonSerialization: Json) : MyParser {
override fun <T> parseContentMessage(json: String, contentSerializer: KSerializer<T>): ContentMessageDto<T> {
return jsonSerialization.decodeFromString(ContentMessageDto.serializer(contentSerializer), json)
}
}
//Usage:
val result = parser.parseContentMessage(jsonToParse, DummyObjectNonNullProperties.serializer())

Kotlin - How to make a property delegate by map with a custom name?

I'm trying to get my head around property delegates, and I have an interesting use case. Is it possible to have something like this:
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any by MapDelegate(properties, "font-size")
}
That would allow me to store fontSize using the map as a delegate, but with a custom key (i.e. "font-size").
The specific use case if for storing things like CSS property tags that can be accessed through variables (fontSize) for use in code, but can be rendered properly when iterating through the map (font-size: 18px;).
The documentation on the delegated properties is a good source of information on the topic. It probably is a bit longer read than the following examples:
fun <T, TValue> T.map(properties: MutableMap<String, TValue>, key: String): ReadOnlyProperty<T, TValue> {
return object : ReadOnlyProperty<T, TValue> {
override fun getValue(thisRef: T, property: KProperty<*>) = properties[key]!!
}
}
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any by map(properties, "font-size")
}
You can ease up things a little bit and avoid typing the CSS property name by converting Kotlin property names to CSS attributes equivalents like so:
fun <T, TValue> map(properties: Map<String, TValue>, naming:(String)->String): ReadOnlyProperty<T, TValue?> {
return object : ReadOnlyProperty<T, TValue?> {
override fun getValue(thisRef: T, property: KProperty<*>) = properties[naming(property.name)]
}
}
object CamelToHyphen : (String)->String {
override fun invoke(camelCase: String): String {
return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, camelCase)
}
}
fun <T, TValue> T.cssProperties(properties: Map<String,TValue>) = map(properties, CamelToHyphen)
class MyClass {
val properties = mutableMapOf<String, Any>()
val fontSize: Any? by cssProperties(properties)
}
The above sample uses Guava's CaseFormat.
If you'd like to have mutable property your delegate will have to implement setter method:
fun <T, TValue> map(properties: MutableMap<String, TValue?>, naming: (String) -> String): ReadWriteProperty<T, TValue?> {
return object : ReadWriteProperty<T, TValue?> {
override fun setValue(thisRef: T, property: KProperty<*>, value: TValue?) {
properties[naming(property.name)] = value
}
override fun getValue(thisRef: T, property: KProperty<*>) = properties[naming(property.name)]
}
}