pbuchheit
pbuchheit

Reputation: 1607

Checking if a string with leading zeros is a valid integer in Kotlin

I'm working on a Kotlin function that needs to validate some data before making an API call. Part of that validation is verifying that an incoming parameter string represents a valid non-negative integer. My initial thought was to use String.toInt() and catch the NumberFormatException. Something like:

data class Foo(val port_range_from: String?)
{
    init{
        try{
            require( port_range_from != null && port_range_from.toInt() > -1)
            {
                "port_range_from must be non-negative integer."
            }
        }
        catch (e:NumberFormatException)
        {
            throw IllegalArgumentException("port_range_from must be non-negative integer.")
        }
    }
}

While this works for most inputs, there is a corner case. String.toInt() will strip off leading zeros and return the integer value with no exceptions. The API I am calling however, treats strings with leading zeros as invalid and will throw an error if I pass in such a value. So a value like "012" will pass my validation but will cause an error if passed to the API.

Is there a 'kotlin' way to detect Strings with leading zeros without having to manually parse through the string to find them myself?

Update:

I eventually settled on using a Regex to check the input String. I'm still not sure if this is the best solution.

require( Regex("^0$|^[1-9]+[0-9]*$").matches(port_range_from))
            {
                "port_range_from must be non-negative integer."
            }

Upvotes: 2

Views: 248

Answers (3)

AlexT
AlexT

Reputation: 2964

Regex can be used here, but it is way too heavy and expensive for such a simple check.

We can use discrete statements to check for your requirements. It will also make the code more readable and the messages would be specific to the actual issue of that particular input.

init {
    //check that the input is not null
    require(port_range_from != null) { "port_range_from must not be null" }

    val portInt = port_range_from.toIntOrNull()

    //if portInt is null, it means that the String could not be parsed to Int
    require(portInt != null) { "port_range_from must be a valid integer" }

    //we don't need to check for leading 0s, if the input is actually just `0` 
    if (port_range_from.length > 1) require(!port_range_from.startsWith('0')) { "port_range_from must not have leading 0s" }

    //check to see that the port is not negative
    require(portInt > -1) { "port_range_from must be non-negative integer" }
}

We can then do stuff with portInt or not, we know that if we call port_range_from.toInt() afterwards, it will have a value that we want.


We could also combine the first two require and just do:

val portInt = port_range_from?.toIntOrNull()
require(portInt != null) { "port_range_from must be a non-null valid integer" }

But then again, merging different checks in the same require muddles the reason the input was rejected.


Another things is that this may be an XY problem. If we could use a normal class, we could ignore the leading 0s in the input, and just create a new Int field without them.

class Foo(port_range_from: String?) {

    val port_range_from : Int

    init {
        //check that the input is not null
        require(port_range_from != null) { "port_range_from must not be null" }

        val portInt = port_range_from.toIntOrNull()

        //if portInt is null, it means that the String could not be parsed to Int
        require(portInt != null) { "port_range_from must be a valid integer" }
        
        //check to see that the port is not negative
        require(portInt > -1) { "port_range_from must be non-negative integer" }

        this.port_range_from = portInt
    }
}

Upvotes: 1

user2340612
user2340612

Reputation: 10713

Another option is to check if the parsed number is the same as the original string – which is the case only if there's no leading zero.

fun isValid(n: String): Boolean {
    val number = n.toIntOrNull()
    return n == number?.toString() && number >= 0
}

Upvotes: 0

rph
rph

Reputation: 2639

It's acceptable to use a regex like you mentioned, it's a common technique for input validation. But if, for any reason, you are looking for a non-regex alternative for this specific situation, you should also be able to go with this:

require(port_range_from?.toIntOrNull()?.let { it > -1 && it.toString() == port_range_from } ?: false) {
    "port_range_from must be non-negative integer."
}

Converting the int back to a string and comparing it to the original string, is what makes sure there are no leading zeros.

In my opinion, in terms of readability, the regex does a better job.

Upvotes: 1

Related Questions