Skip to content

Instantly share code, notes, and snippets.

@nvh

nvh/Examples.swift

Last active Aug 29, 2015
Embed
What would you like to do?
Functional JSON Parsing in Swift
//MARK: examples
extension JSONValue {
static func url(value: JSONType) -> JSONResult<NSURL> {
return cast(value).map({NSURL(string: $0)})
}
}
extension JSONValue {
static func gender(value: JSONType) -> JSONResult<Gender> {
return cast(value).map({ (string: String) -> Gender in
var gender : Gender = .Unknown
if(string == "male") {
gender = .Male
} else if (string == "female") {
gender = .Female
}
return gender
})
}
}
struct Address: JSONModel {
var city: String
var street: String
var number: NSNumber
static func create(city: String)(street: String)(number: Int) -> Address {
return Address(city: city,street: street, number: number)
}
static func decode(json: JSONObject) -> JSONResult<Address> {
return Address.create <^> json.value("city")
<*> json.value("street")
<*> json.value("number")
}
}
enum Gender {
case Male
case Female
case Unknown
}
struct User: JSONModel {
var firstName: String
var lastName: String
var gender: Gender
var websites: [NSURL]
var address: Address
var awesome: Bool?
static func create(firstName: String)
(lastName: String)
(gender: Gender)
(websites: [NSURL])
(address: Address)
(awesome: Bool?)
-> User {
return User(firstName: firstName,lastName:lastName, gender: gender, websites: websites, address: address, awesome: awesome)
}
static func decode(json: JSONObject) -> JSONResult<User> {
return User.create <^> json["firstName"] >>= JSONValue.string
<*> json.value("lastName")
<*> json["gender"] >>= JSONValue.gender
<*> json["websites"] >>= JSONValue.url
<*> json["address"] >>= parse
<*> json.optional("awesome") >>= JSONValue.bool
}
}
let test = ["{\"firstName\":\"Niels\",\"lastName\":\"van Hoorn\",\"gender\":\"male\",\"websites\":[\"http://zekerwaar.nl\",\"http://codecoverage.nl\",\"http://nvh.io\"],\"awesome\":true,\"address\":{\"street\":\"Neude\",\"number\":4,\"city\":\"Utrecht\"}}"
,"{\"firstName\":\"Chris\",\"lastName\":\"Eidhof\",\"gender\":\"male\",\"websites\":[\"http://objc.io\",\"http://chris.eidhof.nl\"]}"]
for json in test {
if let jsonObject: JSONType = parseString(json) {
println(jsonObject)
let result : JSONResult<User> = parse(jsonObject)
switch(result) {
case let .Failure(error):
println(error)
default:()
}
var error : NSError? = nil
if let user : User? = parse(jsonObject, &error) {
user
error
println(user)
println(user?.awesome)
println(user?.address.city)
}
}
}
let list = "[{\"firstName\":\"Niels\",\"lastName\":\"van Hoorn\",\"gender\":\"male\",\"websites\":[\"http://zekerwaar.nl\",\"http://codecoverage.nl\",\"http://nvh.io\"],\"awesome\":true,\"address\":{\"street\":\"Neude\",\"number\":4,\"city\":\"Utrecht\"}},{\"firstName\":\"Chris\",\"lastName\":\"Eidhof\",\"gender\":\"male\",\"websites\":[\"http://objc.io\",\"http://chris.eidhof.nl\"],\"address\":{\"street\":\"a\",\"city\":\"Berlin\",\"number\":2}}]"
if let jsonObject: JSONType = parseString(list) {
println(jsonObject)
if let arr = jsonObject as? [JSONType] {
let result : JSONResult<[User]> = parseList(arr)
switch(result) {
case let .Failure(error):
println(error)
case let .Success(box):
println(box.value)
}
}
}
import Foundation
public typealias JSONType = AnyObject
func fail<A>(message: String) -> JSONResult<A> {
return .Failure(NSError(domain: "ParseJSONDomain", code: 0, userInfo: [NSLocalizedDescriptionKey:message]))
}
func pure<T>(value: T) -> JSONResult<T> {
return .Success(Box(value))
}
func cast<T,U>(value: T) -> JSONResult<U> {
if let a = value as? U {
return pure(a)
} else {
return fail("Failed to cast \(value) to \(U.self)")
}
}
func sequence<T>(list: [JSONResult<T>]) -> JSONResult<[T]> {
var result : [T] = []
for element in list {
switch(element) {
case let .Success(box):
result.append(box.value)
case let .Failure(error):
return fail("Sequence failed: \(error)")
}
}
return pure(result)
}
func mapM<T,U>(transform: T -> JSONResult<U>, list: [T]) -> JSONResult<[U]> {
return sequence(list.map(transform))
}
func forM<T,U>(list: [T], transform: T -> JSONResult<U>) -> JSONResult<[U]> {
return mapM(transform, list)
}
func optional<T>(value: T) -> T? {
let v : T? = value
return v
}
//Boxing of variable is needed because Generic Typed Enum's aren't (yet?) supported in Swift
class Box<T> {
let value: T!
init(_ value: T) { self.value = value }
}
//Functor extension
extension Box {
func map<U>(transform: (T) -> U) -> Box<U> {
return Box<U>(transform(self.value))
}
}
enum JSONResult<T> {
case Success(Box<T>)
case Failure(NSError)
}
//Functor extension
extension JSONResult {
func map<U> (transform: T -> U) -> JSONResult<U> {
switch self {
case let JSONResult.Success(box):
return JSONResult<U>.Success(box.map(transform))
case let JSONResult.Failure(error):
return JSONResult<U>.Failure(error)
}
}
}
//Applicative extension
extension JSONResult {
func sequence<U>(transform: JSONResult<T -> U>) -> JSONResult<U> {
switch (transform) {
case let .Success(transformBox):
switch(self) {
case let .Success(box):
return .Success(box.map(transformBox.value))
case let .Failure(error):
return .Failure(error)
}
case let .Failure(error):
return .Failure(error)
}
}
}
//Monadic Extension
extension JSONResult {
func bind<U>(f : T -> JSONResult<U>) -> JSONResult<U> {
switch(self) {
case let .Success(box):
return f(box.value)
case let .Failure(error):
return .Failure(error)
}
}
}
//MARK: operators
infix operator >>= { associativity left precedence 200 }
infix operator <^> { associativity left precedence 150 }
infix operator <*> { associativity left precedence 150 }
func >>= <T,U> (value : JSONResult<T>, transform: T -> JSONResult<U>) -> JSONResult<U> {
return value.bind(transform)
}
//Array bind
func >>= <U> (value : JSONResult<JSONType>, transform: JSONType -> JSONResult<U>) -> JSONResult<[U]> {
return value.bind(cast).bind({forM($0, transform)})
}
//Optional bind
func >>= <U> (value : JSONResult<JSONType>, transform: JSONType -> JSONResult<U>) -> JSONResult<U?> {
return value.bind( {(v: JSONType?) -> JSONResult<U?> in
if let a: JSONType = v {
if let b = a as? NSNull {
return pure(nil)
}
return transform(a).map(optional)
} else {
return pure(nil)
}
})
}
func <*><T, U>(transform: JSONResult<T -> U>, value: JSONResult<T>) -> JSONResult<U> {
return value.sequence(transform)
}
//<$> in Haskell
func <^><T, U>(transform: T -> U, value: JSONResult<T>) -> JSONResult<U> {
return value.map(transform)
}
protocol JSONDecodable {
class func decode(jsonObject: JSONObject) -> JSONResult<Self>
}
protocol JSONModel : JSONDecodable {
}
//MARK: value parsing
struct JSONObject {
var object : [String:JSONType]
subscript(key: String) -> JSONResult<JSONType> {
if let value: JSONType = object[key] {
return pure(value)
} else {
return fail("JSON Object \(object) has no key '\(key)'")
}
}
func optional(key:String) -> JSONResult<JSONType> {
if let value: JSONType = object[key] {
return pure(value)
} else {
return pure(NSNull())
}
}
func value<T>(key: String) -> JSONResult<T> {
return self[key].bind(JSONValue.decode)
}
}
struct JSONValue {
static func decode<T>(value: JSONType) -> JSONResult<T> {
return cast(value)
}
static func string(value: JSONType) -> JSONResult<String> {
return decode(value)
}
static func number(value: JSONType) -> JSONResult<NSNumber> {
return decode(value)
}
static func number(value: JSONType) -> JSONResult<Int> {
return self.number(value) >>= {pure($0.integerValue)}
}
static func number(value: JSONType) -> JSONResult<Double> {
return self.number(value) >>= {pure($0.doubleValue)}
}
static func number(value: JSONType) -> JSONResult<Float> {
return self.number(value) >>= {pure($0.floatValue)}
}
static func bool(value: JSONType) -> JSONResult<Bool> {
return self.number(value) >>= {pure($0.boolValue)}
}
static func object(value: JSONType) -> JSONResult<[String:JSONType]> {
return decode(value)
}
}
//MARK: parsing
func parseString(jsonString: String) -> JSONType! {
var json : JSONType? = nil
if let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) {
json = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
}
return json
}
func parse<A: JSONDecodable>(json: JSONType) -> JSONResult<A> {
return cast(json).map({JSONObject(object: $0)}).bind({ A.decode($0) })
}
func parseList<A: JSONDecodable>(list: [JSONType]) -> JSONResult<[A]> {
return forM(list, parse)
}
func parse<A: JSONDecodable>(json: JSONType, inout error: NSError?) -> A? {
let result : JSONResult<A> = parse(json)
var returnValue: A? = nil
switch(result) {
case let .Success(box):
returnValue = box.value
case let .Failure(e):
error = e
}
return returnValue
}
@nvh

This comment has been minimized.

Copy link
Owner Author

@nvh nvh commented Oct 3, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.