Typed Returns
More Concise, More Swift-y.
EDIT: Thanks to my co-worker Eric Slosser for catching that my optional getter wasn't properly checking to see if the value was NSNULL()
if the JSON included explicit null
values returned by the server.
I was writing some code that had to parse a JSON blob, and quickly realized that I was writing the same checks over and over, particularly checking to see if a required key was nil, then if it was the correct type. After some fiddling, I managed to encapsulate a lot of that code into a pair of extensions on Dictionary
.
import Foundation
enum JSONDecodingError: ErrorType {
case NilValueForProperty
case TypeMismatch
}
extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
// Adds a Swift-y way to get a non-optional type from a key
func getValue<TypedReturn>(key: Key) throws -> TypedReturn {
guard let valueObject = self[key] else {
throw JSONDecodingError.NilValueForProperty
}
guard let value = valueObject as? TypedReturn else {
throw JSONDecodingError.TypeMismatch
}
return value
}
// Adds a Swift-y way to get a optional type from a key
func getOptionalValue<TypedReturn>(key: Key) throws -> TypedReturn? {
if let valueObject = self[key] where valueObject !== NSNull() {
guard let value = valueObject as? TypedReturn else {
throw JSONDecodingError.TypeMismatch
}
return value
}
return nil
}
}
let values: [String: AnyObject] = [
"a": 2,
"b" :"Test",
"c": 4
]
do {
let a: Int = try values.getValue("a")
}
catch {
error
}
do {
let b: Int = try values.getValue("b")
}
catch {
error
}
do {
let q: Int = try values.getValue("q")
}
catch {
error
}
do {
let q: Int? = try values.getOptionalValue("q")
}
catch {
error
}
do {
let b: Int? = try values.getOptionalValue("b")
}
catch {
error
}
Copied and pasted into a playground, this will return values in the rows that have the correct type, error for rows that have the wrong type, and error for missing rows unless getOptionalValue
is used. I wanted to use the same method signature and automatically swap implementations for Optional values, but I couldn't figure out how to constrain the first function to only apply to non optional values, and without that constraint the functions collide on any optional value.
There's a lot of interesting ways to tune the idea to make it better, but it made the code I was working on much more concise while still capturing error data to help with troubleshooting any errors coming in on the import.