Paul S
Paul S

Reputation: 494

how to elegantly create a map with only non-null values in Kotlin

How can I rewrite this code without having to resort to a MutableMap and conversion to immutable map?

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
    val map = mutableMapOf("key1" to mandatoryValue)
    optionalValue?.let { map.put("key2", it) }
    return map.toMap()
}

My alternative solution isn't very nice either because it needs an unsafe cast:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mapOf(
        "key1" to mandatoryValue,
        "key2" to optionalValue
    ).filterValues { it != null } as Map<String, String>

What I am looking for is something in the lines of:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mapOf(
        "key1" to mandatoryValue,
        optionalValue?.let { "key2" to it }
    )

Upvotes: 4

Views: 3127

Answers (4)

Somaiah Kumbera
Somaiah Kumbera

Reputation: 7489

In kotlin 1.6

buildMap {
    put("key1", mandatoryValue)

    if (optionalValue != null)
        put("key2", optionalValue)
}

This creates a mutableMap under the sheets, but is quite elegant and readable <3

Upvotes: 1

Adam Millerchip
Adam Millerchip

Reputation: 23091

There's nothing wrong with using MutableMap - in fact your first solution (without the redundant toMap()) is already pretty elegant. It's simpler and clearer than any immutable answer will be. Immutable operations come at the cost of additional object creations and copies, so unless you need the guarantees of immutability, it's best to use a MutableMap but only expose it via the Map interface, as you are already doing.

If you really wanted to do it immutably, you could do it like this:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mapOf("key1" to mandatoryValue) +
        (optionalValue?.let { mapOf("key2" to it) } ?: emptyMap())

Or equivalently if you prefer:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mapOf("key1" to mandatoryValue) +
        if (optionalValue != null) mapOf("key2" to optionalValue) else emptyMap()

Or:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
    val mandatoryMap = mapOf("key1" to mandatoryValue)
    return optionalValue?.let { mandatoryMap + ("key2" to optionalValue) } ?: mandatoryMap
}

Or:

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
    val mandatoryMap = mapOf("key1" to mandatoryValue)
    return if (optionalValue != null) mandatoryMap + ("key2" to optionalValue) else mandatoryMap
}

Upvotes: 3

Jo&#227;o Dias
Jo&#227;o Dias

Reputation: 17460

You can have your own extension function as follows and then use it to filter null values from a Map:

fun <K, V> Map<K, V?>.filterValuesNotNull() = 
    mapNotNull { (k, v) -> v?.let { k to v } }.toMap()

Upvotes: 3

Tenfour04
Tenfour04

Reputation: 93581

toMap() does not necessarily create an immutable map. It is only guaranteed to be read-only. The underlying class instance might be a MutableMap (which in the current implementation is true if it has more than one key). Therefore, toMap() in your first block of code is unnecessary. The MutableMap is automatically upcast to Map when you return it since you specified Map as the return type. So, you could have put

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> {
    val map = mutableMapOf("key1" to mandatoryValue)
    optionalValue?.let { map.put("key2", it) }
    return map
}

or

fun createMap(mandatoryValue: String, optionalValue: String?): Map<String, String> =
    mutableMapOf("key1" to mandatoryValue).apply {
        if (optionalValue != null) put("key2", optionalValue)
    }

To get the syntax you requested in your last example, you could create an overload of mapOf that accepts and filters null values:

fun <K, V> mapOf(vararg pairs: Pair<K, V>?): Map<K, V> =
    mapOf(*pairs.filterNotNull().toTypedArray())

Upvotes: 3

Related Questions