'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?
Solution 1:[1]
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
}
Solution 2:[2]
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.
Solution 3:[3]
This yields no warnings kotlin 1.5.30
listOfNotNull(
nullableString?.let { "key1" to it },
nullableString?.let { "key2" to it }
).toMap()
Solution 4:[4]
Just some idea of a few extensions you could make off the top of my head
fun <K, V, T> List<T>.associateNotNullValues(fn: (T)-> Pair<K, V?>): Map<K, V> =
mapNotNull { v ->
fn(v).takeIf { it.second != null }
?.let { Pair(it.first, it.second!!) }
}.toMap()
fun <K, V, T> List<T>.associateNotNullKeys(fn: (T)-> Pair<K?, V>): Map<K, V> =
mapNotNull { v ->
fn(v).takeIf { it.first != null }
?.let { Pair(it.first!!, it.second) }
}.toMap()
fun <K, V, T> List<T>.associateNotNullKeysAndValues(fn: (T)-> Pair<K?, V?>): Map<K, V> =
mapNotNull { v ->
fn(v).takeIf { it.first != null && it.second != null }
?.let { Pair(it.first!!, it.second!!) }
}.toMap()
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | JB Nizet |
Solution 2 | Community |
Solution 3 | Matt Broekhuis |
Solution 4 | aeskreis |