Skip to content

Instantly share code, notes, and snippets.

@maxchuquimia
Created September 20, 2016 03:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maxchuquimia/21c26930fc89b6f83933fbfa937f1d04 to your computer and use it in GitHub Desktop.
Save maxchuquimia/21c26930fc89b6f83933fbfa937f1d04 to your computer and use it in GitHub Desktop.
Seralizing JSON in Swift without casting
//
// Final implementation, as described in
// http://nspasteboard.com/2016/09/09/reducing-the-number-of-lines-used-to-serialize-from-json/
//
// This example has been updated to Swift 3.0 (Xcode 8S193k)
//
import Foundation
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Implementation (required)
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
typealias JSON = [String: AnyObject]
/// Provides a way to track the success or failure of a task
enum Response<O> {
case Success(value: O)
case Failure(error: NSError)
/// A simple way to create a failure
static func FailureMessage(message: String) -> Response {
let error = NSError(domain: "", code: 1, userInfo: [NSLocalizedDescriptionKey: message])
return .Failure(error: error)
}
/// If `self` is `.Success(_)`, returns `O`, else returns `nil`
var successValue: O? {
switch self {
case let .Success(val): return val
default: return nil
}
}
/// If `self` is `.Success(_)`, returns `O`, else throws an error
func value() throws -> O {
switch self {
case let .Success(val):
return val
case let .Failure(error):
throw error
}
}
}
/// A protocol defining an object that can be serialised from a JSON dictionary
protocol JSONSerializable {
static func with(json: JSON) -> Response<Self>
}
/// A function that takes a function and converts it's return value or thrown value into a `Response`
func response<T>(block: ((Void) throws -> T)) -> Response<T> {
let val: T
do {
val = try block()
} catch let e as NSError {
print("Response<\(T.self)> Error: \(e.localizedDescription)")
return .Failure(error: e)
}
return .Success(value: val)
}
extension Sequence where Iterator.Element == JSON {
func flatMapResponse<T>(block: ((Iterator.Element) -> Response<T>)) -> [T] {
return self.map(block).flatMap({$0.successValue})
}
}
func flatMapJSONSerializable<T, X where X: JSONSerializable>(json: [JSON], object: X.Type) -> [T] {
return json.map(object.with).flatMap({$0.successValue as? T})
}
extension Dictionary {
/// Base getter for primitive non-optionals
func get<T>(_ x: Key) throws -> T {
guard let o = self[x] as? T else {
throw NSError(domain: "", code: 1, userInfo: [NSLocalizedDescriptionKey: "Cannot parse `\(x)` as `\(T.self)`"])
}
return o
}
/// Base getter for primitive optionals
func getOptional<T>(_ x: Key) -> T? {
return self[x] as? T
}
/// Getter for JSONSerializable non-optionals
func get<T where T: JSONSerializable>(_ x: Key) throws -> T {
do {
return try T.with(json: get(x)).value()
} catch let e as NSError {
throw e
}
}
/// Getter for JSONSerializable optionals
func getOptional<T where T: JSONSerializable>(_ x: Key) -> T? {
return response {
return try T.with(json: self.get(x)).value()
}
.successValue
}
/// Getter for [JSONSerializable] non-optionals
func get<A where A: JSONSerializable >(_ x: Key) throws -> [A] {
do {
return try flatMapJSONSerializable(json: get(x), object: A.self)
} catch let e {
throw e
}
}
/// Getter for [JSONSerializable] optionals
func getOptional<A where A: JSONSerializable >(_ x: Key) -> [A]? {
return response {
return try flatMapJSONSerializable(json: self.get(x), object: A.self)
}
.successValue
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Models
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
struct RegionModel: JSONSerializable {
struct Location: JSONSerializable {
let lat: Double
let lon: Double
let radius: Double
static func with(json: JSON) -> Response<Location> {
return response {
return try Location(
lat: json.get("lat"),
lon: json.get("lon"),
radius: json.get("radius")
)
}
}
}
let identifier: Int
let title: String
let message: String
let location: Location
static func with(json: JSON) -> Response<RegionModel> {
return response {
return try RegionModel(
identifier: json.get("id"),
title: json.get("title"),
message: json.get("message"),
location: json.get("location")
)
}
}
}
struct RegionPage: JSONSerializable {
let items: [RegionModel]
let hasMoreRows: Bool
static func with(json: JSON) -> Response<RegionPage> {
return response {
return try RegionPage(
items: json.get("items"),
hasMoreRows: json.get("hasMoreRows")
)
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Test Data
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let json: JSON = [
"id": 57,
"title": "Restricted Region",
"message": "You may not fly in this area.",
"location": [
"lat": -33.865143,
"lon": 151.209900,
"radius": 3500.0
]
]
let pageJSON: JSON = [
"items" : [
json,
json
],
"hasMoreRows": false
]
print(RegionPage.with(json: pageJSON))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment