However, to extract individual components from a quote
(symbol, price, etc.)
the regular expression must contain capture groups,
of which there are two varieties:
positional capture groups and
named capture groups.
Positional capture groups are demarcated in the pattern
by enclosing parentheses ((___)).
With some slight modifications,
we can make original regular expression capture each part of the stock quote:
When matched,
the symbol can be accessed by the first capture group,
the price by the second,
and so on.
For large numbers of capture groups —
especially in patterns with nested groups —
one can easily lose track of which parts correspond to which positions.
So another approach is to assign names to capture groups,
which are denoted by the syntax (?<NAME>___).
Note:
Most regular expression engines —
including the one used by NSRegularExpression —
provide a mode to ignore whitespace;
this lets you segment long patterns over multiple lines,
making them easier to read and understand.
Theoretically, this approach allows you to access each group by name
for each match of the regular expression.
In practice, doing this in Swift can be inconvenient,
as it requires you to interact with cumbersome NSRegularExpression APIs
and somehow incorporate it into your model layer.
RegularExpressionDecoder provides a convenient solution
to constructing Decodable objects from regular expression matches
by automatically matching coding keys to capture group names.
And it can do so safely,
thanks to the new ExpressibleByStringInterpolation protocol in Swift 5.
To understand how,
let’s start by considering the following Stock model,
which adopts the Decodable protocol:
struct Stock: Decodable {
let symbol: String
var price: Double
enum Sign: String, Decodable {
case gain = "▲"
case unchanged = "="
case loss = "▼"
}
private var sign: Sign
private var change: Double = 0.0
var movement: Double {
switch sign {
case .gain: return +change
case .unchanged: return 0.0
case .loss: return -change
}
}
}
So far, so good.
Now, normally, the Swift compiler
automatically synthesizes conformance to Decodable,
including a nested CodingKeys type.
But in order to make this next part work correctly,
we’ll have to do this ourselves:
extension Stock {
enum CodingKeys: String, CodingKey {
case symbol
case price
case sign
case change
}
}
Here’s where things get really interesting:
remember our regular expression with named capture patterns from before?
We can replace the hard-coded names
with interpolations of the Stock type’s coding keys.
Note:
This example benefits greatly from another new feature in Swift 5:
raw string literals.
Those octothorps (#) at the start and end
tell the compiler to ignore escape characters (\)
unless they also include an octothorp (\#( )).
Using raw string literals,
we can write regular expression metacharacters like \b, \d, and \s
without double escaping them (i.e. \\b).
Thanks to ExpressibleByStringInterpolation,
we can restrict interpolation segments to only accept those coding keys,
thereby ensuring a direct 1:1 match between capture groups
and their decoded properties.
And not only that —
this approach lets us to verify that keys have valid regex-friendly names
and aren’t captured more than once.
It’s enormously powerful,
allowing code to be incredibly expressive
without compromising safety or performance.
When all is said and done,
RegularExpressionDecoder lets you decode types
from a string according to a regular expression pattern
much the same as you might from JSON or a property list
using a decoder:
Regular Expression Decoder
A decoder that constructs objects from regular expression matches.
For more information about creating your own custom decoders, consult Chapter 7 of the Flight School Guide to Swift Codable. For more information about using regular expressions in Swift, check out Chapter 6 of the Flight School Guide to Swift Strings.
Requirements
Usage
Explanation
Let’s say that you’re building an app that parses stock quotes from a text-based stream of price changes.
Each stock is represented by the following structure:
▲
), loss (▼
), or no change (=
)These format constraints lend themselves naturally to representation by a regular expression, such as:
This regular expression can distinguish between valid and invalid stock quotes.
However, to extract individual components from a quote (symbol, price, etc.) the regular expression must contain capture groups, of which there are two varieties: positional capture groups and named capture groups.
Positional capture groups are demarcated in the pattern by enclosing parentheses (
(___)
). With some slight modifications, we can make original regular expression capture each part of the stock quote:When matched, the symbol can be accessed by the first capture group, the price by the second, and so on.
For large numbers of capture groups — especially in patterns with nested groups — one can easily lose track of which parts correspond to which positions. So another approach is to assign names to capture groups, which are denoted by the syntax
(?<NAME>___)
.Theoretically, this approach allows you to access each group by name for each match of the regular expression. In practice, doing this in Swift can be inconvenient, as it requires you to interact with cumbersome
NSRegularExpression
APIs and somehow incorporate it into your model layer.RegularExpressionDecoder
provides a convenient solution to constructingDecodable
objects from regular expression matches by automatically matching coding keys to capture group names. And it can do so safely, thanks to the newExpressibleByStringInterpolation
protocol in Swift 5.To understand how, let’s start by considering the following
Stock
model, which adopts theDecodable
protocol:So far, so good.
Now, normally, the Swift compiler automatically synthesizes conformance to
Decodable
, including a nestedCodingKeys
type. But in order to make this next part work correctly, we’ll have to do this ourselves:Here’s where things get really interesting: remember our regular expression with named capture patterns from before? We can replace the hard-coded names with interpolations of the
Stock
type’s coding keys.Thanks to
ExpressibleByStringInterpolation
, we can restrict interpolation segments to only accept those coding keys, thereby ensuring a direct 1:1 match between capture groups and their decoded properties. And not only that — this approach lets us to verify that keys have valid regex-friendly names and aren’t captured more than once. It’s enormously powerful, allowing code to be incredibly expressive without compromising safety or performance.When all is said and done,
RegularExpressionDecoder
lets you decode types from a string according to a regular expression pattern much the same as you might from JSON or a property list using a decoder:License
MIT
Contact
Mattt (@mattt)