Jon889
(Jonathan Bailey)
1
I am getting some data from a network response and it comes in as a string but can really be multiple types of data such as an Int, Bool, Date. Is there a nicer way to do this than multiple if statements?
Currently I have (in an enum init)
let value: String = someFieldFromNetworkResponse
if let value = Int(value) {
self = .int(value)
} else if let value = parseDate(value) {
self = .data(value)
} else if let value = parseBool(value) {
self = .bool(value)
} else {
self = .unknown
}
With the actual use case being to parse an associated enum:
public enum IdentificationChallengeNotice: JSONObjectConvertible, Equatable {
case remainingAttempts(_ attempts: Int)
case validUntil(_ date: Date)
case passcodeMatches(_ matches: Bool)
case other(type: String, value: String)
public init(jsonDictionary: JSONDictionary) throws {
let type: String = jsonDictionary.json(atKeyPath: "type") ?? .unknown
let value: String = jsonDictionary.json(atKeyPath: "value") ?? ""
if type == "remainingAttempts", let value = Int(value) {
self = .remainingAttempts(value)
} else if type == "validUntil", let value = parseDate(value) {
self = .validUntil(value)
} else if ... {
...
} else {
self = .other(type: type, value: value)
}
}
I'm trying to do something like this but it doesn't seem possible to have a let in a where clause:
let value: String = someFieldFromNetworkResponse
switch value {
case let v where let value = Int(v):
self = .int(value)
// etc
}
or even something like this would be even more ideal:
switch value {
case let value = Int($0):
self = .int(value)
// etc
}
cukr
2
There's a different way, but I'm not sure if it's nicer or not
func parseBool(_ str: String) -> Bool? {
return Bool(str)
}
enum Foo {
case int(Int)
case bool(Bool)
case unknown
init(string: String) {
self = Int(string).map { .int($0) } ?? parseBool(string).map { .bool($0) } ?? .unknown
}
}
Jon889
(Jonathan Bailey)
3
Hmm interesting. Sorry I should have shared the use case where there's a type field that drives which type it is:
public enum IdentificationChallengeNotice: JSONObjectConvertible, Equatable {
case remainingAttempts(_ attempts: Int)
case validUntil(_ date: Date)
case passcodeMatches(_ matches: Bool)
case other(type: String, value: String)
public init(jsonDictionary: JSONDictionary) throws {
let type: String = jsonDictionary.json(atKeyPath: "type") ?? .unknown
let value: String = jsonDictionary.json(atKeyPath: "value") ?? ""
if type == "remainingAttempts", let value = Int(value) {
self = .remainingAttempts(value)
} else if type == "validUntil", let value = parseDate(value) {
self = .validUntil(value)
} else if ... {
...
} else {
self = .other(type: type, value: value)
}
}
cukr
4
In that case a bunch of ifs is the nicest solution
Here's a bonus, in case your keyboard breaks and no longer is able to type `if`
import Foundation
func parseDate(_ str: String) -> Date? { Date() }
public enum IdentificationChallengeNotice {
case remainingAttempts(_ attempts: Int)
case validUntil(_ date: Date)
case passcodeMatches(_ matches: Bool)
case other(type: String, value: String)
public init() throws {
let type: String = "foo"
let value: String = "bar"
self = {
switch type {
case "remainingAttempts":
return Int(value).map { Self.remainingAttempts($0) }
case "validUntil":
return parseDate(value).map { Self.validUntil($0) }
default:
return nil
}
}() ?? Self.other(type: type, value: value)
}
}
There are probably some ways to push this into a switch, such as:
func ~=<T>(pattern: (String) -> T?, value: String) -> Bool {
return pattern(value) != nil
}
switch (type, value) {
case ("remainingAttempts", Int.init): self = .remainingAttempts(Int(value)!)
case ("validUntil", parseDate): self = .validUntil(extractDate(value))
...
default: .other(type: type, value: value)
}
but it makes me think that switch is probably not the right tool for this kind of thing.
I don't personally think there's anything wrong with just using if statements and you can use return to break up your if statements to make it look a little cleaner:
if type == "remainingAttempts", let intValue = Int(value) {
self = .remainingAttempts(intValue)
return
}
if type == "validUntil", let dateValue = parseDate(value) {
self = .validUntil(dateValue)
return
}
...
self = .other(type: type, value: value)
Jon889
(Jonathan Bailey)
6
Yep, I'm find with the if else's it just seems like it's so close to being possible in a switch statement that I thought I was missing something.
public enum IdentificationChallengeNotice: JSONObjectConvertible, Equatable {
case remainingAttempts(_ attempts: Int)
case validUntil(_ date: Date)
case passcodeMatches(_ matches: Bool)
case other(type: String, value: String)
public init(jsonDictionary: JSONDictionary) throws {
let type: String = jsonDictionary.json(atKeyPath: "type") ?? .unknown
let value: String = jsonDictionary.json(atKeyPath: "value") ?? ""
switch type {
case "remainingAttempts", where let value = Int(value):
self = .remainingAttempts(value)
case "validUntil", where let value = parseDate(value):
self = .validUntil(value)
// etc
default:
self = .other(type: type, value: value)
}
}
Seems like valid swift, except you can't have a let value = ... in a where clause. I'm not really sure why not either.
I believe it’s because the case evaluation has to return a Bool i.e case foo and case foo where foo.bar evaluates to a true/false value which is used to decide whether the match is successful or not. I suppose an optional binding (where let a = b) could be supported by treating it as a Bool, however if you really want to do optional binding, the only way to do it now would be inside the case statement:
// For convenience.
func other() -> Self {
return .other(type: type, value: value)
}
switch type {
case “validUntil”:
if let value = parseDate(value) {
self = .validUntil(value)
} else {
self = other()
}
...
}