Swift Tip: Non-Empty Optionals
In our Swift Talk backend, we need to display some information that comes from a third-party API. For example, we receive the following data:
struct BillingInfo {
var name: String
var vatNumber: String?
}
Here's a simplified computed property that displays the data:
extension BillingInfo {
var lines: [String] {
return [
"Name: \(name)",
vatNumber.map { number in
"Your VAT Number is: \(number)"
} ?? "No VAT Number."
]
}
}
Note that in the cases where we don't have a VAT number, we display a different text. Unfortunately, sometimes the API we work with also returns an empty VAT number. As a first try, we dealt with it like this:
return [
"Name: \(name)",
vatNumber.map { number in
number.isEmpty ? "No VAT Number." : "Your VAT Number is: \(number)"
} ?? "No VAT Number."
]
Of course, the duplication isn't very nice, so we tried another approach:
return [
"Name: \(name)",
vatNumber?.isEmpty == false ? "Your VAT Number is: \(vatNumber!)" : "No VAT Number."
]
Unfortunately, now we have a force-unwrap, which we usually try to avoid. Another variant would be to use an if let
, and pull out the formatted VAT string into a variable:
let vatString: String
if let number = vatNumber, !number.isEmpty {
vatString = "Your VAT Number is: \(number)"
} else {
vatString = "No VAT Number."
}
return [
"Name: \(name)",
vatString
]
The if let
variant is safe, and has no duplication, but it involves quite a bit more code. The nice thing about the other two approaches was that the transformations were very local: they happened at the point of usage.
Even though it's a very minor piece of code, we didn't really like any of the solutions above. Instead, we wrote a helper that solves this in a more general way:
extension Optional where Wrapped: Collection {
var nonEmpty: Wrapped? {
return self?.isEmpty == true ? nil : self
}
}
The nonEmpty
property returns nil
when the collection within the optional is empty. This allows us to collapse both checks (for nil
and for isEmpty
) into one:
return [
"Name: \(name)",
vatNumber.nonEmpty.map { "Your VAT Number is: \($0)" } ?? "No VAT Number."
]
We're almost certain that we aren't the first to come up with this solution, but if you haven't seen it yet, we figured it might still be useful.
Our Server-Side Swift Collection covers a range of related topics. We'll be talking more about our backend rewrite soon.
To support our work, please subscribe, or give a gift.