Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to filter out null Any? values in Kotlin Map?

I'm trying to think of a function that would allow a Map<String, Any?> object to be treated as Map<String,Any> through type inference through applying a single function.

I am pretty new to the transformation functions in Kotlin and have tried the various filter and filterValues filterNot on the map like so:

val input = mapOf(Pair("first",null))
val filtered: Map<String,Any> = input.filter { it.value!=null }

it also fails to compile with any of these

input.filterValues { it!=null }
input.filterNot { it.value==null }
input.filterNot { it.value is Nothing }

The closest I can seem to get is applying multiple steps or having an Unchecked cast warning. I would have thought that filtering the values to be !=null would suffice. My only other thought is that it's due to the generics?

like image 713
Harry J Avatar asked Feb 18 '17 08:02

Harry J


People also ask

How do you filter a map in Kotlin?

There are also two specific ways for filtering maps: by keys and by values. For each way, there is a function: filterKeys() and filterValues() . Both return a new map of entries which match the given predicate. The predicate for filterKeys() checks only the element keys, the one for filterValues() checks only values.

Can map accept null values?

Map doesn't allow duplicate keys, but it allows duplicate values. HashMap and LinkedHashMap allows null keys and null values but TreeMap doesn't allow any null key or value.


2 Answers

The filter functions return a Map with the same generic types as the original map. To transform the type of the value, you need to map the values from Any? to Any, by doing a cast. The compiler can't know that the predicate you pass to filter() makes sure all the values of the filtered map are non-null, so it can't use type inference. So your best et is to use

val filtered: Map<String, Any> = map.filterValues { it != null }.mapValues { it -> it.value as Any }

or to define a function doing the filtering and the transformation in a single pass, and thus be able to use smart casts:

fun filterNotNullValues(map: Map<String, Any?>): Map<String, Any> {
    val result = LinkedHashMap<String, Any>()
    for ((key, value) in map) {
        if (value != null) result[key] = value
    }
    return result
}
like image 179
JB Nizet Avatar answered Sep 22 '22 08:09

JB Nizet


The compiler just doesn't perform type analysis deep enough to infer that, for example, input.filterValues { it != null } filters out null values from the map and thus the resulting map should have a not-null value type. Basically there can be arbitrary predicate with arbitrary meaning in terms of types and nullability.

There is no special case function for filtering null values out of a map in the stdlib (like there is .filterIsInstance<T>() for iterables). Therefore your easiest solution is to apply an unchecked cast thus telling the compiler that you are sure about the type safety not being violated:

@Suppress("UNCHECKED_CAST")
fun <K, V> Map<K, V?>.filterNotNullValues() = filterValues { it != null } as Map<K, V>

See also: another question with a similar problem about is-check.

like image 34
hotkey Avatar answered Sep 22 '22 08:09

hotkey