kotlindictionarygenericscastingtype-safety

How to safety cast raw Map to Map<String, List<Int>> in Kotlin?


I need a function which will cast Map to Map<String, List<Int>>

Currently I use unsafe "as" but I receive a fair warning about it. I want to fix it.

For list part I was able to implement function

inline fun <reified T : Any> List<*>.checkItemsAre(): List<T> {
    return this.filterIsInstance<T>().takeIf { it.size == this.size }
        ?: throw IllegalArgumentException("blablabla")
}

But I can't fund analog for map.

How can I fix it ?


Solution

  • If you are looking for something similar to the checkItemsAre for Lists that you showed, you can write such a method for Maps.

    inline fun <reified K : Any, reified V: Any> Map<*, *>.checkKeyValuesAre() =
        entries.associate {
            Pair(
                (it.key as? K) ?: throw IllegalArgumentException(),
                (it.value as? V) ?: throw IllegalArgumentException()
            )
        }
    

    Just like checkItemsAre, this creates a new Map if all the keys are of type K and all the values are of type V.

    However, this wouldn't completely check nested generics like Map<String, List<Int>>. If you want to handle that, you would have to add special cases for List, e.g.

    return if (typeOf<V>().classifier == List::class) {
        val listElemType = typeOf<V>().arguments[0].type?.classifier as KClass<*>
        entries.associate {
            Pair(
                (it.key as? K) ?: throw IllegalArgumentException(),
                (it.value as List<*>).checkItemsAre(listElemType)
            )
        }
    } else {
        // the code in the first code snippet...
    }
    
    fun <T : Any> List<*>.checkItemsAre(clazz: KClass<T>): List<T> {
        return this.filterIsInstance(clazz.java).takeIf { it.size == this.size }
            ?: throw IllegalArgumentException()
    }
    

    If you are only going to handle maps with values of type List, I would just assume the map's values are lists:

    inline fun <reified K : Any, reified E: Any> Map<*, *>. checkKeyAndListValuesAre() =
        entries.associate {
            Pair(
                (it.key as? K) ?: throw IllegalArgumentException(),
                (it.value as List<*>).checkItemsAre<E>()
            )
        }