Skip to content

Instantly share code, notes, and snippets.

@jqsilver
Last active August 29, 2015 14:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jqsilver/51171d29785ac49f343a to your computer and use it in GitHub Desktop.
Save jqsilver/51171d29785ac49f343a to your computer and use it in GitHub Desktop.
Building models from json - inspired by CommandShift
import Foundation
// Turning the attempt() function into an object with state, primarly to get rid of the inout param
// http://commandshift.co.uk/blog/2014/12/28/nice-web-services-swift-edition/
protocol BlankInitable {
init()
}
extension Int : BlankInitable {}
extension String : BlankInitable {}
extension NSDate : BlankInitable {}
class JSONExtractor {
let dict: [String: Any]
var failed: Bool = false // if you attempt to get a value and it doesn't work
var errors = [String: String]()
init(_ dict: [String: Any]) {
self.dict = dict
}
// SwiftyJSON has typed accessors for each primitive type
// So you could get rid of these methods if you're using that
func getString(key: String) -> String {
return getVal(key)
}
func getInt(key: String) -> Int {
return getVal(key)
}
func getVal<Type : BlankInitable>(key: String) -> Type {
if let val = dict[key] {
if let actual = val as? Type {
return actual
} else {
return fail(key, reason: "expected \(Type.self)")
}
} else {
return fail(key, reason: "not present")
}
}
func fail<Type : BlankInitable>(key: String, reason: String) -> Type {
failed = true
errors[key] = reason
return Type()
}
}
// Experimental usage examples
class Thing {
let myStr: String
init(myStr: String) {
self.myStr = myStr
}
init?(dict: [String: Any]) {
let extractor = JSONExtractor(dict)
self.myStr = extractor.getString("my_str")
// self.myInt = extractor.getInt("my_int")
// etc
if extractor.failed == true {
return nil
}
}
class func fromJSON(dict: [String: Any]) -> (ThingObject?, NSError?) {
let extractor = JSONExtractor(dict)
let myStr = extractor.getString("my_str")
// let myInt = extractor.getInt("my_int")
// etc
if extractor.failed == false {
return (nil, NSError())
} else {
return (ThingObject(myStr: myStr), nil)
}
}
}
// Trying with a map and KVC
class ThingObject: NSObject {
let myStr: String
init(myStr: String) {
self.myStr = myStr
}
init?(json: [String: Any]) {
self.myStr = "" // ugh
super.init()
let extractor = JSONExtractor(json)
let map: [(String, String, String -> Any)] = [
("myStr", "my_str", extractor.getString),
("myInt", "my_int", extractor.getInt),
]
// These ended up not working because setValue only works with AnyObject
for (objectKey, jsonKey, extractorFunc) in map {
// setValue(extractorFunc(jsonKey), forKey: objectKey)
}
setValue(extractor.getString("my_str"), forKey: "myStr")
if extractor.failed == false {
return nil
}
}
}
@jrturton
Copy link

jrturton commented Jan 8, 2015

Shouldn't

var failed: Bool = true // if you attempt to get a value and it doesn't work

Be

var failed: Bool = false // if you attempt to get a value and it doesn't work

And

if extractor.failed == false {

be

if extractor.failed == true {

You never set failed to false otherwise?

@jqsilver
Copy link
Author

Sorry, I didn't notice your comment until now, but you are correct. I eventually discovered that when I started writing tests.

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