Created
March 3, 2018 05:46
-
-
Save twof/126fe584e3896b4a226afa49f68418f3 to your computer and use it in GitHub Desktop.
Codable that automatically decodes with default vals
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 | |
func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> { | |
var i = 0 | |
return AnyIterator { | |
let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) } | |
if next.hashValue != i { return nil } | |
i += 1 | |
return next | |
} | |
} | |
extension Dictionary { | |
func toJSON() throws -> Data? { | |
return try? JSONSerialization.data(withJSONObject: self, options: []) | |
} | |
} | |
extension KeyedDecodingContainer { | |
func decode<T>(_ key: K, or defaultValue: T) throws -> T where T : Decodable { | |
return try decodeIfPresent(T.self, forKey: key) ?? defaultValue | |
} | |
func decode<T>(_ key: K) throws -> T where T : Decodable { | |
return try decode(T.self, forKey: key) | |
} | |
} | |
extension Decodable { | |
func autoDecode<U>(_ key: U, from container: KeyedDecodingContainer<U>) throws -> Self { | |
return try container.decodeIfPresent(Self.self, forKey: key) ?? self | |
} | |
} | |
extension AnyIterator where Element: CodingKey { | |
func caseWhere<T>(_ val: String) -> T? { | |
return self.first { (key) -> Bool in | |
return String(describing: key) == val | |
} as? T | |
} | |
} | |
func attemptCast<T>(of val: Any) -> T? where T: Codable { | |
if let val = val as? Double { | |
return val as? T | |
} else if let val = val as? Int { | |
return val as? T | |
} else if let val = val as? String { | |
return val as? T | |
} else { | |
return nil | |
} | |
} | |
protocol Keyable { | |
associatedtype Keys: CodingKey, Hashable | |
} | |
class ReflectiveDecodable: NSObject, Decodable { | |
public init<T>(from decoder: Decoder, with keys: T.Type) throws where T: CodingKey & Hashable { | |
super.init() | |
let container = try decoder.container(keyedBy: T.self) | |
let enumProps = iterateEnum(T.self) | |
let mirroredObject = Mirror(reflecting: self) | |
let props = mirroredObject.children | |
for attr in props { | |
let key: T? = enumProps.caseWhere(attr.label!) | |
if let key = key { | |
try self.setVal(forKey: key, keyString: attr.label!, attrValue: attr.value, container: container) | |
} | |
} | |
} | |
private func setVal<T>(forKey key: T, keyString: String, attrValue: Any, container: KeyedDecodingContainer<T>) throws { | |
switch attrValue { | |
case let intValue as Int: | |
let value = try container.decode(key, or: intValue) | |
self.setValue(value, forKey: keyString) | |
case let stringValue as String: | |
let value = try container.decode(key, or: stringValue) | |
self.setValue(value, forKey: keyString) | |
case let boolValue as Bool: | |
let value = try container.decode(key, or: boolValue) | |
self.setValue(value, forKey: keyString) | |
case let doubleValue as Double: | |
let value = try container.decode(key, or: doubleValue) | |
self.setValue(value, forKey: keyString) | |
case let dateValue as Date: | |
let value = try container.decode(key, or: dateValue) | |
self.setValue(value, forKey: keyString) | |
default: | |
self.setValue("0", forKey: keyString) | |
} | |
} | |
} | |
class ManyProperties: ReflectiveDecodable { | |
@objc var name: String = "Alex" | |
@objc var age: Int = 21 | |
@objc var favoriteColor: String = "green" | |
enum CodingKeys: String, CodingKey { | |
case name | |
case age | |
case favoriteColor = "favorite_color" | |
} | |
public required init(from decoder: Decoder) throws { | |
try super.init(from: decoder, with: CodingKeys.self) | |
} | |
} | |
class Pet: ReflectiveDecodable { | |
@objc var name: String = "Odie" | |
@objc var numberOfLegs: Int = 7 | |
@objc var owner: String = "John" | |
enum CodingKeys: String, CodingKey { | |
case name | |
case numberOfLegs = "number_of_legs" | |
case owner | |
} | |
public required init(from decoder: Decoder) throws { | |
try super.init(from: decoder, with: CodingKeys.self) | |
} | |
} | |
do { | |
let json = try ["name":"Rex"].toJSON() | |
let sut = try JSONDecoder().decode(Pet.self, from: json!) | |
print(sut.numberOfLegs, sut.owner, sut.name) | |
} catch { | |
print(error) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment