or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

backported-collections.mdcollection-extensions.mdindex.mdjava-conversion.mdresource-management.mdstring-parsing.mdutility-features.md
tile.json

string-parsing.mddocs/

String Parsing

Safe string parsing methods that return Option types instead of throwing exceptions. These methods provide consistent parsing across all numeric types and handle edge cases gracefully.

Capabilities

Safe Numeric Parsing

Extension methods for String that safely parse to numeric types, returning None for invalid input instead of throwing exceptions.

implicit class StringOps(s: String) {
  /**
   * Parse string to Boolean safely
   * Accepts "true"/"false" (case-insensitive)
   * @return Some(boolean) if valid, None otherwise
   */
  def toBooleanOption: Option[Boolean]
  
  /**
   * Parse string to Byte safely
   * @return Some(byte) if valid and within Byte range, None otherwise
   */
  def toByteOption: Option[Byte]
  
  /**
   * Parse string to Short safely
   * @return Some(short) if valid and within Short range, None otherwise
   */
  def toShortOption: Option[Short]
  
  /**
   * Parse string to Int safely
   * @return Some(int) if valid and within Int range, None otherwise
   */
  def toIntOption: Option[Int]
  
  /**
   * Parse string to Long safely
   * @return Some(long) if valid and within Long range, None otherwise
   */
  def toLongOption: Option[Long]
  
  /**
   * Parse string to Float safely
   * Handles special values like "NaN", "Infinity", "-Infinity"
   * @return Some(float) if valid, None otherwise
   */
  def toFloatOption: Option[Float]
  
  /**
   * Parse string to Double safely
   * Handles special values like "NaN", "Infinity", "-Infinity"
   * @return Some(double) if valid, None otherwise
   */
  def toDoubleOption: Option[Double]
}

Usage Examples:

import scala.collection.compat._

// Integer parsing
"42".toIntOption        // Some(42)
"abc".toIntOption       // None
"2147483648".toIntOption // None (exceeds Int.MaxValue)
"".toIntOption          // None
"  123  ".toIntOption   // Some(123) - whitespace trimmed

// Boolean parsing
"true".toBooleanOption   // Some(true)
"TRUE".toBooleanOption   // Some(true)
"false".toBooleanOption  // Some(false)
"FALSE".toBooleanOption  // Some(false)
"yes".toBooleanOption    // None
"1".toBooleanOption      // None

// Floating point parsing
"3.14".toDoubleOption    // Some(3.14)
"NaN".toFloatOption      // Some(Float.NaN)
"Infinity".toDoubleOption // Some(Double.PositiveInfinity)
"-Infinity".toFloatOption // Some(Float.NegativeInfinity)
"invalid".toDoubleOption  // None

// Byte range validation
"127".toByteOption       // Some(127)
"128".toByteOption       // None (exceeds Byte.MaxValue)
"-128".toByteOption      // Some(-128)
"-129".toByteOption      // None (below Byte.MinValue)

// Long parsing with large numbers
"9223372036854775807".toLongOption  // Some(Long.MaxValue)
"9223372036854775808".toLongOption  // None (exceeds Long.MaxValue)

Safe Parsing Patterns

Common patterns for using safe parsing methods in real-world applications.

Configuration Reading:

import scala.collection.compat._

def readConfig(properties: Map[String, String]): Config = {
  Config(
    port = properties.get("server.port").flatMap(_.toIntOption).getOrElse(8080),
    timeout = properties.get("timeout.seconds").flatMap(_.toLongOption).getOrElse(30L),
    debugEnabled = properties.get("debug.enabled").flatMap(_.toBooleanOption).getOrElse(false),
    maxMemory = properties.get("max.memory.gb").flatMap(_.toDoubleOption).getOrElse(2.0)
  )
}

CSV Parsing:

import scala.collection.compat._

def parseCsvRow(row: String): Option[Person] = {
  val fields = row.split(",")
  if (fields.length >= 3) {
    for {
      name <- Option(fields(0)).filter(_.nonEmpty)
      age <- fields(1).toIntOption
      salary <- fields(2).toDoubleOption
    } yield Person(name, age, salary)
  } else None
}

// Usage
val csvData = List(
  "Alice,25,50000.0",
  "Bob,30,60000.0", 
  "Charlie,invalid,70000.0",  // Will be None due to invalid age
  "Diana,28,80000.0"
)

val people = csvData.flatMap(parseCsvRow)

JSON-like String Processing:

import scala.collection.compat._

def parseJsonValue(value: String): Option[JsonValue] = {
  value.trim match {
    case s if s.startsWith("\"") && s.endsWith("\"") => 
      Some(JsonString(s.slice(1, s.length - 1)))
    case s => 
      s.toIntOption.map(JsonInt(_))
        .orElse(s.toDoubleOption.map(JsonDouble(_)))
        .orElse(s.toBooleanOption.map(JsonBoolean(_)))
  }
}

Input Validation:

import scala.collection.compat._

case class ValidationError(field: String, message: String)

def validateUserInput(input: Map[String, String]): Either[List[ValidationError], User] = {
  val errors = List.newBuilder[ValidationError]
  
  val name = input.get("name") match {
    case Some(n) if n.nonEmpty => Some(n)
    case _ => 
      errors += ValidationError("name", "Name is required")
      None
  }
  
  val age = input.get("age").flatMap(_.toIntOption) match {
    case Some(a) if a >= 0 && a <= 150 => Some(a)
    case Some(_) =>
      errors += ValidationError("age", "Age must be between 0 and 150")
      None
    case None =>
      errors += ValidationError("age", "Valid age is required")
      None
  }
  
  val email = input.get("email") match {
    case Some(e) if e.contains("@") => Some(e)
    case _ =>
      errors += ValidationError("email", "Valid email is required")
      None
  }
  
  val errorList = errors.result()
  if (errorList.isEmpty) {
    Right(User(name.get, age.get, email.get))
  } else {
    Left(errorList)
  }
}

Numeric Range Validation:

import scala.collection.compat._

def parsePort(portStr: String): Option[Int] = {
  portStr.toIntOption.filter(port => port > 0 && port <= 65535)
}

def parsePercentage(percentStr: String): Option[Double] = {
  percentStr.toDoubleOption.filter(pct => pct >= 0.0 && pct <= 100.0)
}

def parseTimeout(timeoutStr: String): Option[Long] = {
  timeoutStr.toLongOption.filter(_ > 0)
}

// Usage with validation
def configureServer(config: Map[String, String]): Either[String, ServerConfig] = {
  for {
    port <- config.get("port")
      .flatMap(parsePort)
      .toRight("Invalid port number")
    timeout <- config.get("timeout")
      .flatMap(parseTimeout)  
      .toRight("Invalid timeout value")
    successRate <- config.get("success.rate")
      .flatMap(parsePercentage)
      .toRight("Invalid success rate percentage")
  } yield ServerConfig(port, timeout, successRate)
}

Error Handling Best Practices

Combining with Validation:

import scala.collection.compat._

// Combine multiple optional parses
def parseCoordinate(x: String, y: String): Option[(Double, Double)] = {
  for {
    xVal <- x.toDoubleOption
    yVal <- y.toDoubleOption
  } yield (xVal, yVal)
}

// Parse with default values
def parseWithDefaults(config: Map[String, String]): AppConfig = {
  AppConfig(
    threads = config.get("threads").flatMap(_.toIntOption).getOrElse(4),
    memory = config.get("memory").flatMap(_.toLongOption).getOrElse(1024L),
    ratio = config.get("ratio").flatMap(_.toDoubleOption).getOrElse(0.75)
  )
}

// Parse with validation chains
def parsePositiveInt(s: String): Option[Int] = {
  s.toIntOption.filter(_ > 0)
}

def parseNonEmptyString(s: String): Option[String] = {
  Option(s).filter(_.trim.nonEmpty)
}

Implementation Details

The string parsing methods handle various edge cases:

  • Whitespace: Leading and trailing whitespace is automatically trimmed
  • Empty Strings: Return None rather than throwing exceptions
  • Overflow: Numbers outside the target type's range return None
  • Special Float Values: "NaN", "Infinity", "-Infinity" are properly parsed for Float/Double
  • Case Sensitivity: Boolean parsing is case-insensitive
  • Null Safety: Null strings are handled gracefully

These parsing methods provide a safe, functional approach to string conversion that integrates well with Scala's Option-based error handling patterns.