Simple yet powerful object mapping made possible by Swift 2's new error handling. Greatly inspired by Argo, but without a bizillion functional operators.
struct Repository {
let name: String
let description: String
let stargazersCount: Int
let language: String?
let sometimesMissingKey: String?
let owner: User // Struct conforming to Decodable
let defaultBranch: Branch // Struct NOT conforming to Decodable
var fullName: String { return "\(owner.login)/\(name)" }
}
extension Repository: Decodable {
static func decode(j: AnyObject) throws -> Repository {
return try Repository(
name: j => "name",
description: j => "description",
stargazersCount: j => "stargazers_count",
language: j => "language",
sometimesMissingKey: try? j => "sometimesMissingKey",
owner: j => "owner",
defaultBranch: Branch(name: j => "default_branch")
)
}
}
public protocol Decodable {
static func decode(json: AnyObject) throws -> Self
}
public func parse<T>(json: AnyObject, path: [String], decode: (AnyObject throws -> T)) throws -> T
which call the parse
-function.
/// Try to decode as T, or throw
public func => <T: Decodable>(lhs: AnyObject, rhs: String) throws -> T
/// Do not decode. Without an inferred return type, this overload will be called.
public func => (lhs: AnyObject, rhs: String) throws -> AnyObject
/// Try to decode as T, or throw. Will return nil if the object at the keypath is NSNull.
public func => <T: Decodable>(lhs: AnyObject, rhs: String) throws -> T?
// MARK: Arrays
/// Try to decode as NSArray, and decode each element as T. Will throw if decoding of any element in the array throws. I.e, if one element is faulty the entire array is "thrown away".
public func => <T: Decodable>(lhs: AnyObject, rhs: String) throws -> [T]
/// Try to decode as NSArray, and decode each element as T. Will return nil if the object at the keypath is NSNull. Will throw if decoding of any element in the array throws. I.e, if one element is faulty the entire array is "thrown away".
public func => <T: Decodable>(lhs: AnyObject, rhs: String) throws -> [T]?
/// Try to decode as NSArray, and decode each element as T or nil, if the element is NSNull.
public func => <T: Decodable>(lhs: AnyObject, rhs: String) throws -> [T?]
/// Enables parsing nested objects e.g json => "a" => "b"
/// Uses \u{0} (null) as a separator
public func => (lhs: String, rhs: String) -> String
public enum DecodingError {
public struct Info {
var path: [String]
var object: AnyObject?
var rootObject: AnyObject?
}
case MissingKey(key: String, info: Info)
case TypeMismatch(type: Any.Type, info: Info)
}
Example:
let dict: NSDictionary = ["object": ["repo": ["owner": ["id" : 1, "login": "anviking"]]]]
do {
let username: String = try dict => "object" => "repo" => "owner" => "lllloogon"
} catch let error {
print(error)
}
===============================
MissingKey at object.repo.owner: lllloogon in {
id = 1;
login = anviking;
}
The Decodable
-protocol and the =>
-operator should in no way make you committed to use them everywhere.
For example you could...
-
Skip adapting the
Decodable
protocol, and parse things differently depending on the context (likedefaultBranch
in the example code). -
Create your own throwing decode-functions, e.g for
NSDate
, or convenience-extensions with your own date-formatter.
public class func decode(json: AnyObject) throws -> Self {
let string = try String.decode(json)
guard let date = ISO8601DateFormatter.dateFromString(string) else {
throw NSDateDecodingError.InvalidStringFormat
}
return self.init(timeIntervalSince1970: date.timeIntervalSince1970)
}
- You can use
Decodable
with classes. Just make sure to either call arequired
initializer on self (e.gself.init
) and returnSelf
, or make your classfinal
. ( This might be a problem though)