Skip to content

Instantly share code, notes, and snippets.

@twof
Created March 3, 2018 05:46
Show Gist options
  • Save twof/126fe584e3896b4a226afa49f68418f3 to your computer and use it in GitHub Desktop.
Save twof/126fe584e3896b4a226afa49f68418f3 to your computer and use it in GitHub Desktop.
Codable that automatically decodes with default vals
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