Create a gist now

Instantly share code, notes, and snippets.

@khanlou /Parser.swift Secret
Created Apr 8, 2016

What would you like to do?
struct ParserError: ErrorType {
let message: String
}
struct Parser {
let dictionary: [String: AnyObject]?
init(dictionary: [String: AnyObject]?) {
self.dictionary = dictionary
}
func fetch<T>(key: String) throws -> T {
let fetchedOptional = dictionary?[key]
guard let fetched = fetchedOptional else {
throw ParserError(message: "The key \"\(key)\" was not found.")
}
guard let typed = fetched as? T else {
throw ParserError(message: "The key \"\(key)\" was not the right type. It had value \"\(fetched).\"")
}
return typed
}
func fetchOptional<T>(key: String) throws -> T? {
let fetchedOptional = dictionary?[key]
guard let fetched = fetchedOptional else {
return nil
}
guard let typed = fetched as? T else {
throw ParserError(message: "The key \"\(key)\" was not the right type. It had value \"\(fetched).\"")
}
return typed
}
func fetch<T, U>(key: String, transformation: T -> U?) throws -> U {
let fetched: T = try fetch(key)
guard let transformed = transformation(fetched) else {
throw ParserError(message: "The value \"\(fetched)\" at key \"\(key)\" could not be transformed.")
}
return transformed
}
func fetchOptional<T, U>(key: String, transformation: T -> U?) -> U? {
return (dictionary?[key] as? T).flatMap(transformation)
}
func fetchArray<T, U>(key: String, transformation: T -> U?) throws -> [U] {
let fetched: [T] = try fetch(key)
return fetched.flatMap(transformation)
}
}
enum SomeEnum: String {
case FirstCase = "first_case"
case SecondCase = "second_case"
}
struct InnerType {
let aString: String
let anInt: Int
let stringArray: [String]
let anOptionalValue: String?
init?(dictionary: [String: AnyObject]?) {
let parser = Parser(dictionary: dictionary)
do {
self.aString = try parser.fetch("a_string")
self.anInt = try parser.fetch("an_int")
self.stringArray = try parser.fetch("string_array")
self.anOptionalValue = try parser.fetchOptional("bingo")
} catch let error {
print(error)
return nil
}
}
}
class OuterType {
let inner: InnerType
let enums: [SomeEnum]
init?(dictionary: [String: AnyObject]?) {
let parser = Parser(dictionary: dictionary)
do {
self.inner = try parser.fetch("inner") { InnerType(dictionary: $0) }
self.enums = try parser.fetchArray("enums") { SomeEnum(rawValue: $0) }
} catch let error {
print(error)
return nil
}
}
}
var innerDictionary = [String: AnyObject]()
innerDictionary["a_string"] = "some_string" as? AnyObject
innerDictionary["an_int"] = 3 as? AnyObject
innerDictionary["string_array"] = ["a", "bunch", "of", "strings"] as? AnyObject
InnerType(dictionary: innerDictionary)
var outerDictionary = [String: AnyObject]()
outerDictionary["inner"] = innerDictionary as? AnyObject
outerDictionary["enums"] = ["first_case", "second_case"] as? AnyObject
let outer = OuterType(dictionary: outerDictionary)
outer?.inner.stringArray
outer?.enums

I really like your approach it is clean straight forward and probably faster than 3rd party libraries.
I came across one thing though, if you have an API returning "null" for optional values you can not separately type check in fetchOptional.
Because the guard let checking for the existence will not fail and return, so it will always throw an error if the optional value is not there.
This only works if the API does not return that field at all, like you simulate with:
self.anOptionalValue = try parser.fetchOptional("bingo")
One can prevent that by type checking right away like this:

    func fetchOptional<T>(key: String) -> T? {
        guard let fetched = dictionary?[key] else {
            return nil
        }
        return typed
    }

I was not able to find another way right now. If you can think of anything let me know I would greatly appreciate it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment