Sabo
Sabo

Reputation: 1677

In Kotlin, what is the best way to make `String? + String?` return `null` if any of the inputs are `null`?

The problem:

Consider the following code:

val key: String? = "key"
val value: String? = "label"
val row = key + ": " + value

I would like the variable row to be null if any of the supplied inputs in the concatenation is null.

By default, any null String will be converted to "null" and the concatenation will proceed. In example:

val value = null
"Height: " + value + "mm" // Produces: "Height: nullmm"

I can skip the showing "null" in the results by using value ?: "", but this solves only a part of my problem:

val value = null
"Height: " + (value ?: "") + "mm" // Produces: "Height: mm"

My best solution so far:

I understand that writing a simple function like the one below would do the job, but I still expect that something like this already exists in the language:

fun Array<String?>.nullSafeConcat(): String? {
    val result = StringBuilder()
    this.forEach {
        if(it == null) return null
        result.append(it)
    }
    
    return result.toString()
}

The ask:

Is there a better way to do this?

Post Scriptum:

I cannot understand why would a null string be converted to "null" by default, as I cannot find any use case where this would be actually usable.

Upvotes: 0

Views: 946

Answers (3)

matt freake
matt freake

Reputation: 5090

I'm not sure if this solves the problem, you can override the + operator on a nullable String to get close to what you want. For example:

private operator fun String?.plus(otherString: String?): String? = if (this==null || otherString ==null ) "null"  else this + otherString

Then:

    fun main() {
    val s1: String? = "Hello"
    val s2: String? = null
    val s3: String? = "Bye"
    println(s1 + s2)
    println(s2 + s1)
    println(s1 + s3)
}

prints:

null
null
HelloBye

The problem is it will only work with variables of String? not String which your ":" value is. So you'd need to do something like:

s1 + colon + s2

where colon was also of type String?

EDIT:

There are two dangers with this approach. Firstly If you don't make it private (or even if you do) there is a risk that existing or new code tries to append two String? values and gets your new behaviour, which they don't expect. Secondly, someone reading the code where you call it may be surprised by the behaviour if they don't spot that you've overridden the + operator.

Upvotes: 2

zmunm
zmunm

Reputation: 436

How about this

listOfNotNull("foo", null, "bar", "baz").joinToString()

Upvotes: 0

Utku &#214;zdemir
Utku &#214;zdemir

Reputation: 7725

I think matt freake's answer is correct for the question, but I would avoid overriding the + operator for Strings - it can cause tons of unexpected issues.

Instead, I suggest you to slightly modify your nullSafeConcat helper function to be a standalone function that takes vararg instead of being an extension function. Something like this:

fun nullSafeConcat(vararg strings: String?): String? {
    val result = StringBuilder()
    strings.forEach {
        if(it == null) return null
        result.append(it)
    }
    
    return result.toString()
}

Then you can use it like:

val merged = nullSafeConcat("foo", null, "bar", "baz")

Notes:
You might want to handle the empty case (when varargs argument strings is empty) specifically, depending on the outcome you want.
Additionally, if you want this to work for at least 2 strings (so a concatenation is actually meaningful), you can use a signature like nullSafeConcat(first: String?, second: String?, vararg rest: String?) instead.

Upvotes: 2

Related Questions