kotlingenerics

Cannot check for instance of erased type


I have a function which accepts one parameter - Any. There is a when block where I check all possibile types I want to support and throw in case the type is unknown.

fun toAttributeValue(value: Any): AttributeValue {
    return when (value) {
        ...
        is Map<*, *> -> {
            ....
        }
        is List<*> -> {
            ...
        }
        ...
    }
}

In case of a Map, I need to make sure it's of type Map<String, Any>, so there cannot be a key which is not a String, and there cannot be a null value as well and the reason for that is the fact I call some method inside this block that accepts the Map<String, Any> as it suppose to.

I tried some options, but cannot find the best one, so for now I do:

val mappedValues = value.entries
    .filter { it.key is String && it.value != null }
    .associate { it.key as String to it.value!! }

require(mappedValues.size == value.size) {
    "Unsupported map type"
}

method(mappedValues)

In case of a List, I need to make sure not a single value is null, otherwise throw an exception as well. Should I do similar way I did we the Map, or is there a more handy way to do it?


Solution

  • Instead of filtering the map/list, use methods like associateBy or map to convert the map/list to the desired type, and throw an exception in those functions if a null/non-string is found. After all, you don't want to just remove the nulls/non-strings - your aim is to throw an exception when those are found.

    // suppose 'map' is of type Map<*, *>
    val stringAnyMap: Map<String, Any> = map.entries.associateBy({
        (it.key as? String) ?: throw IllegalArgumentException("some key in the map is null")
    }) {
        it.value ?: throw IllegalArgumentException("the value associated with key '${it.key}' is null")
    }
    
    // suppose 'list' is of type List<*>
    val nonNullList: List<Any> = list.map { it ?: throw IllegalArgumentException("some element in the list is null") }
    

    Note that this also creates a copy of the map/list. This is important because even if the map/list doesn't contain nulls/non-strings at the time when you do this check, such elements could be added to the map/list later on. Therefore, a copy is necessary, so that later changes of the original map/list will not affect the Map<String, Any> or List<Any> that you get as a result of this operation.

    Of course, if you can guarantee that the Map<String, Any> and List<Any> won't escape the toAttributeValue function, and that only a single thread has access to the map/list that is passed in, you can just cast it with an unchecked cast.