Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was helping a junior dev who had written about forty lines of split() and substringIndexOf() calls just to extract a user ID and a session token from a legacy API response string. It looked like a mess of magic numbers and index offsets. The moment the API team added a single space to the output, the whole parser crashed with a StringIndexOutOfBoundsException. He spent an entire afternoon chasing a bug that could have been solved in two lines of code. That's the danger of manual string slicing; it's fragile. This is exactly why you need to get comfortable with Regular Expressions (Regex).

Matching Patterns with the Regex Class

In Kotlin, the Regex class is your primary tool. While you can use the matches() function for a simple true/false check, you'll more often find yourself using containsMatchIn() or find(). The key is to remember that Kotlin provides a very clean way to instantiate these patterns using the Regex constructor or the toRegex() extension function.

Let's look at a real-world example. Suppose you're validating a specific internal product code that must start with two uppercase letters, followed by a hyphen, and end with four digits (e.g., "TS-1234").

val productCodePattern = "[A-Z]{2}-\\d{4}".toRegex()
val input = "The item is TS-1234"

if (productCodePattern.containsMatchIn(input)) {
    println("Found a valid product code!")
}

One thing to keep in mind: because backslashes are escape characters in Kotlin strings, you have to double them (\\d instead of \d). If that bothers you, you can use a raw string—the ones wrapped in triple quotes """—which lets you use single backslashes. I personally always use raw strings for Regex because it makes the pattern much easier to read.

Extracting Data with Groups and Destructuring

Matching is fine, but extracting the actual data is where Regex really earns its keep. Kotlin makes this surprisingly elegant by allowing you to destructure the results of a match. Instead of manually calling groupValues[1], groupValues[2], and so on, you can map the match directly into variables.

Imagine we are parsing a log line like [2023-10-12] ERROR: Connection timed out. We want the date and the error message, but we don't care about the brackets or the "ERROR" label.

val logPattern = """\[(\d{4}-\d{2}-\d{2})] \w+: (.+)""".toRegex()
val logLine = "[2023-10-12] ERROR: Connection timed out"

val matchResult = logPattern.find(logLine)

if (matchResult != null) {
    // Destructuring the group values
    val (date, message) = matchResult.destructured
    println("Date: $date, Message: $message")
}

The destructured property is a lifesaver here. It creates a sequence of the captured groups (the parts in parentheses), allowing you to assign them to variables in one go. Just be careful: if your regex doesn't match, find() returns null, so always wrap this in a null check or use ?.let { ... } to avoid the dreaded NullPointerException.

Cleaning Strings with Replace

Finally, you'll often use Regex not to find things, but to get rid of them. The replace() function in Kotlin can take a Regex object as its first argument. This is incredibly powerful for sanitizing user input or stripping out unwanted characters from a scraped web page.

For instance, if you have a string with inconsistent whitespace—tabs, multiple spaces, and newlines—and you want to collapse it all into single spaces, you can do this:

val messyString = "This    is a \t very  \n messy string."
val cleanString = messyString.replace("""\s+""".toRegex(), " ")
println(cleanString) // "This is a very messy string."

It's a simple operation, but it replaces what would otherwise be a loop of character checks and conditional logic.




📋 Practical Task

The Log Entry Parser Utility

You are tasked with building a utility that processes a list of system logs. Each log entry follows this format: USER_ID: [LEVEL] MESSAGE (e.g., "user123: [INFO] User logged in" or "admin_01: [WARN] Disk space low").

Create a data class called LogEntry with three properties: userId (String), level (String), and message (String). Then, write a function parseLogs(logs: List<String>): List<LogEntry> that uses a single Regex with capturing groups to extract these three components from each line. If a line doesn't match the pattern, it should be ignored.

Requirements:

  • Use a raw string (triple quotes) for your Regex pattern.
  • Use the destructured property to assign values to your LogEntry object.
  • Test your function with at least one valid entry and one malformed entry to ensure your code doesn't crash.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.