I want a conditional element in my map, but there’s no std function to convert a Map<*, T> to Map<*, T!!>
What I have:
val foo: Map<String, String> = mapOf(
"foo" to "bar",
"baz" to if (condition) "bat" else null,
).filterValues { it != null } as Map<String, String> // unsafe cast
What I want:
val foo: Map<String, String> = mapOf(
"foo" to "bar",
"baz" to if (condition) "bat" else null,
).filterNotNullValues()
I could use buildMap, but it’s clunky and experimental:
val foo: Map<String, String> = buildMap<String, String>(
set("foo", "bar")
if (condition) set("baz", "bat)
)
BONUS ROUND!!
Dart has conditional elements, which is what would be perfect here, something like:
val foo: Map<String, String> = mapOf(
"foo" to "bar",
if (condition) "baz" to "bat",
)
It looks almost exactly like what @kotlinIsland requested in the Bonus Round section.
Although we could take it further and create a filterValuesNotNull() function:
fun main() {
val foo: Map<String, String?> = mapOf(
"foo" to "bar",
"baz" to null,
"one" to null,
"two" to "2")
val fooNoNulls: Map<String, String> = foo.filterValuesNotNull() // <-- Here's the function
println(fooNoNulls)
}
fun <T, R> Map<T, R?>.filterValuesNotNull(): Map<T, R> = entries
.asSequence()
.map {
val v = it.value // We must capture the value of the map entry to trust our null check.
if (v != null) it.key to v else null
}
.filterNotNull()
.toMap()
// Here's the sequence version:
/* fun <T, R> Map<T, R?>.filterValuesNotNull(): Map<T, R> = sequence<Pair<T, R>> {
for ((key, value) in entries) {
if (value != null) yield (key to value)
}
}.toMap() */
EDIT:
This is the narrower case of transforming, Map.Entry<T, R?> to Map.Entry<T, R>. The implementation I came up with does that by “dropping” keys by mapping them to a null entry and filtering them out.
The main trouble with doing a null conversion is that you can’t map the values to non-nullable unless you capture and bind them as a new pair, otherwise they could still be null after (and during) the conditional check. We must make a new map and we can’t go by the assumption that the values happened to be null when we checked because they don’t promise to always be not-null.
You can either do what I did and assign a new val to capture the entry value, or use destructuring like the sequence version in the for loop.
Here’s the functional style version using destructuring:
fun <T, R> Map<T, R?>.filterValuesNotNull(): Map<T, R> = entries
.asSequence() // Using sequence to avoid the extra collection allocation. It's optional.
.map { (k, v) -> // Captured both the key and value as new variables, "k" and "v"
if (v != null) k to v else null
}
.filterNotNull()
.toMap()