0
# String Parsing
1
2
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.
3
4
## Capabilities
5
6
### Safe Numeric Parsing
7
8
Extension methods for String that safely parse to numeric types, returning None for invalid input instead of throwing exceptions.
9
10
```scala { .api }
11
implicit class StringOps(s: String) {
12
/**
13
* Parse string to Boolean safely
14
* Accepts "true"/"false" (case-insensitive)
15
* @return Some(boolean) if valid, None otherwise
16
*/
17
def toBooleanOption: Option[Boolean]
18
19
/**
20
* Parse string to Byte safely
21
* @return Some(byte) if valid and within Byte range, None otherwise
22
*/
23
def toByteOption: Option[Byte]
24
25
/**
26
* Parse string to Short safely
27
* @return Some(short) if valid and within Short range, None otherwise
28
*/
29
def toShortOption: Option[Short]
30
31
/**
32
* Parse string to Int safely
33
* @return Some(int) if valid and within Int range, None otherwise
34
*/
35
def toIntOption: Option[Int]
36
37
/**
38
* Parse string to Long safely
39
* @return Some(long) if valid and within Long range, None otherwise
40
*/
41
def toLongOption: Option[Long]
42
43
/**
44
* Parse string to Float safely
45
* Handles special values like "NaN", "Infinity", "-Infinity"
46
* @return Some(float) if valid, None otherwise
47
*/
48
def toFloatOption: Option[Float]
49
50
/**
51
* Parse string to Double safely
52
* Handles special values like "NaN", "Infinity", "-Infinity"
53
* @return Some(double) if valid, None otherwise
54
*/
55
def toDoubleOption: Option[Double]
56
}
57
```
58
59
**Usage Examples:**
60
61
```scala
62
import scala.collection.compat._
63
64
// Integer parsing
65
"42".toIntOption // Some(42)
66
"abc".toIntOption // None
67
"2147483648".toIntOption // None (exceeds Int.MaxValue)
68
"".toIntOption // None
69
" 123 ".toIntOption // Some(123) - whitespace trimmed
70
71
// Boolean parsing
72
"true".toBooleanOption // Some(true)
73
"TRUE".toBooleanOption // Some(true)
74
"false".toBooleanOption // Some(false)
75
"FALSE".toBooleanOption // Some(false)
76
"yes".toBooleanOption // None
77
"1".toBooleanOption // None
78
79
// Floating point parsing
80
"3.14".toDoubleOption // Some(3.14)
81
"NaN".toFloatOption // Some(Float.NaN)
82
"Infinity".toDoubleOption // Some(Double.PositiveInfinity)
83
"-Infinity".toFloatOption // Some(Float.NegativeInfinity)
84
"invalid".toDoubleOption // None
85
86
// Byte range validation
87
"127".toByteOption // Some(127)
88
"128".toByteOption // None (exceeds Byte.MaxValue)
89
"-128".toByteOption // Some(-128)
90
"-129".toByteOption // None (below Byte.MinValue)
91
92
// Long parsing with large numbers
93
"9223372036854775807".toLongOption // Some(Long.MaxValue)
94
"9223372036854775808".toLongOption // None (exceeds Long.MaxValue)
95
```
96
97
### Safe Parsing Patterns
98
99
Common patterns for using safe parsing methods in real-world applications.
100
101
**Configuration Reading:**
102
103
```scala
104
import scala.collection.compat._
105
106
def readConfig(properties: Map[String, String]): Config = {
107
Config(
108
port = properties.get("server.port").flatMap(_.toIntOption).getOrElse(8080),
109
timeout = properties.get("timeout.seconds").flatMap(_.toLongOption).getOrElse(30L),
110
debugEnabled = properties.get("debug.enabled").flatMap(_.toBooleanOption).getOrElse(false),
111
maxMemory = properties.get("max.memory.gb").flatMap(_.toDoubleOption).getOrElse(2.0)
112
)
113
}
114
```
115
116
**CSV Parsing:**
117
118
```scala
119
import scala.collection.compat._
120
121
def parseCsvRow(row: String): Option[Person] = {
122
val fields = row.split(",")
123
if (fields.length >= 3) {
124
for {
125
name <- Option(fields(0)).filter(_.nonEmpty)
126
age <- fields(1).toIntOption
127
salary <- fields(2).toDoubleOption
128
} yield Person(name, age, salary)
129
} else None
130
}
131
132
// Usage
133
val csvData = List(
134
"Alice,25,50000.0",
135
"Bob,30,60000.0",
136
"Charlie,invalid,70000.0", // Will be None due to invalid age
137
"Diana,28,80000.0"
138
)
139
140
val people = csvData.flatMap(parseCsvRow)
141
```
142
143
**JSON-like String Processing:**
144
145
```scala
146
import scala.collection.compat._
147
148
def parseJsonValue(value: String): Option[JsonValue] = {
149
value.trim match {
150
case s if s.startsWith("\"") && s.endsWith("\"") =>
151
Some(JsonString(s.slice(1, s.length - 1)))
152
case s =>
153
s.toIntOption.map(JsonInt(_))
154
.orElse(s.toDoubleOption.map(JsonDouble(_)))
155
.orElse(s.toBooleanOption.map(JsonBoolean(_)))
156
}
157
}
158
```
159
160
**Input Validation:**
161
162
```scala
163
import scala.collection.compat._
164
165
case class ValidationError(field: String, message: String)
166
167
def validateUserInput(input: Map[String, String]): Either[List[ValidationError], User] = {
168
val errors = List.newBuilder[ValidationError]
169
170
val name = input.get("name") match {
171
case Some(n) if n.nonEmpty => Some(n)
172
case _ =>
173
errors += ValidationError("name", "Name is required")
174
None
175
}
176
177
val age = input.get("age").flatMap(_.toIntOption) match {
178
case Some(a) if a >= 0 && a <= 150 => Some(a)
179
case Some(_) =>
180
errors += ValidationError("age", "Age must be between 0 and 150")
181
None
182
case None =>
183
errors += ValidationError("age", "Valid age is required")
184
None
185
}
186
187
val email = input.get("email") match {
188
case Some(e) if e.contains("@") => Some(e)
189
case _ =>
190
errors += ValidationError("email", "Valid email is required")
191
None
192
}
193
194
val errorList = errors.result()
195
if (errorList.isEmpty) {
196
Right(User(name.get, age.get, email.get))
197
} else {
198
Left(errorList)
199
}
200
}
201
```
202
203
**Numeric Range Validation:**
204
205
```scala
206
import scala.collection.compat._
207
208
def parsePort(portStr: String): Option[Int] = {
209
portStr.toIntOption.filter(port => port > 0 && port <= 65535)
210
}
211
212
def parsePercentage(percentStr: String): Option[Double] = {
213
percentStr.toDoubleOption.filter(pct => pct >= 0.0 && pct <= 100.0)
214
}
215
216
def parseTimeout(timeoutStr: String): Option[Long] = {
217
timeoutStr.toLongOption.filter(_ > 0)
218
}
219
220
// Usage with validation
221
def configureServer(config: Map[String, String]): Either[String, ServerConfig] = {
222
for {
223
port <- config.get("port")
224
.flatMap(parsePort)
225
.toRight("Invalid port number")
226
timeout <- config.get("timeout")
227
.flatMap(parseTimeout)
228
.toRight("Invalid timeout value")
229
successRate <- config.get("success.rate")
230
.flatMap(parsePercentage)
231
.toRight("Invalid success rate percentage")
232
} yield ServerConfig(port, timeout, successRate)
233
}
234
```
235
236
### Error Handling Best Practices
237
238
**Combining with Validation:**
239
240
```scala
241
import scala.collection.compat._
242
243
// Combine multiple optional parses
244
def parseCoordinate(x: String, y: String): Option[(Double, Double)] = {
245
for {
246
xVal <- x.toDoubleOption
247
yVal <- y.toDoubleOption
248
} yield (xVal, yVal)
249
}
250
251
// Parse with default values
252
def parseWithDefaults(config: Map[String, String]): AppConfig = {
253
AppConfig(
254
threads = config.get("threads").flatMap(_.toIntOption).getOrElse(4),
255
memory = config.get("memory").flatMap(_.toLongOption).getOrElse(1024L),
256
ratio = config.get("ratio").flatMap(_.toDoubleOption).getOrElse(0.75)
257
)
258
}
259
260
// Parse with validation chains
261
def parsePositiveInt(s: String): Option[Int] = {
262
s.toIntOption.filter(_ > 0)
263
}
264
265
def parseNonEmptyString(s: String): Option[String] = {
266
Option(s).filter(_.trim.nonEmpty)
267
}
268
```
269
270
### Implementation Details
271
272
The string parsing methods handle various edge cases:
273
274
- **Whitespace**: Leading and trailing whitespace is automatically trimmed
275
- **Empty Strings**: Return None rather than throwing exceptions
276
- **Overflow**: Numbers outside the target type's range return None
277
- **Special Float Values**: "NaN", "Infinity", "-Infinity" are properly parsed for Float/Double
278
- **Case Sensitivity**: Boolean parsing is case-insensitive
279
- **Null Safety**: Null strings are handled gracefully
280
281
These parsing methods provide a safe, functional approach to string conversion that integrates well with Scala's Option-based error handling patterns.