Created
December 11, 2016 00:31
-
-
Save NSExceptional/6b2345fba1b3430292686972500be1bc to your computer and use it in GitHub Desktop.
A concise example of deserializing a JSON response into a model object with as little "glue code" as possible.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
typealias JSON = [String: Any] | |
// For joining Dictionaries; contents of `right` take preceedence over `left` | |
func + <K,V> (left: Dictionary<K,V>, right: Dictionary<K,V>?) -> Dictionary<K,V> { | |
guard let right = right else { return left } | |
var left = left | |
right.forEach { key, value in | |
left[key] = value | |
} | |
return left | |
} | |
/// Base class implements JSON parsing logic | |
/// Must inherit from NSObject to be able to use setValue(_:forKey:) | |
class JSONModel: NSObject { | |
init(json: JSON) { | |
super.init() | |
for (property, key) in type(of: self).propertyToJSONKeyPaths { | |
// Don't set nil values to allow for default values | |
if let value = self.parse(keyPath: key, in: json) { | |
self.setValue(value, forKey: property) | |
} | |
} | |
} | |
/// Fetches an optional value from a JSON dictionary given a key path, like "foo.bar.baz" | |
private func parse(keyPath: String, in json: JSON) -> Any? { | |
guard !keyPath.isEmpty else { | |
return nil | |
} | |
// Separate "foo.bar.baz" into ["foo", "bar"] and "baz" | |
var keys = keyPath.components(separatedBy: ".") | |
let last = keys.popLast()! | |
var current: JSON? = json | |
// After this, `current` will be json["foo"]["bar"] | |
for key in keys { | |
if let previous = current { | |
current = previous[key] as? JSON | |
} else { | |
return nil | |
} | |
} | |
// json["foo"]["bar"]["baz"] | |
return current?[last] | |
} | |
/// Subclasses must override | |
class var propertyToJSONKeyPaths: [String: String] { | |
return [:] | |
} | |
} | |
// Base class of custom class hierarchy, no initializer needed | |
class Foo: JSONModel { | |
private(set) var name: String? = nil | |
private(set) var id: Int = 0 | |
private(set) var kind: String = "default" | |
override class var propertyToJSONKeyPaths: [String: String] { | |
return ["name": "user.name", | |
"id": "identifier", | |
"kind": "user.kind"] | |
} | |
} | |
class Bar: Foo { | |
private(set) var password: String? = nil | |
// Inherits JSON mapping from parent, can overwrite key paths if necessary | |
override class var propertyToJSONKeyPaths: [String: String] { | |
return super.propertyToJSONKeyPaths + ["password": "user.info.password"] | |
} | |
override var description: String { | |
return "\(self.name ?? "no name"), \(self.id), \(self.kind), \(self.password ?? "no password")" | |
} | |
} | |
var json: JSON = ["identifier": 5, | |
"user": ["name": "bob", | |
"info": ["password": "p4ssw0rd"]]] | |
var test = Bar(json: json) // bob, 5, default, p4ssw0rd |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
+1