Skip to content

Instantly share code, notes, and snippets.

@NSExceptional
Created December 11, 2016 00:31
Show Gist options
  • Save NSExceptional/6b2345fba1b3430292686972500be1bc to your computer and use it in GitHub Desktop.
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.
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
@kfarst
Copy link

kfarst commented Dec 13, 2016

+1

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