Skip to content

Instantly share code, notes, and snippets.

@nvh
Last active August 29, 2015 14:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nvh/ff72db507d760a964ab2 to your computer and use it in GitHub Desktop.
Save nvh/ff72db507d760a964ab2 to your computer and use it in GitHub Desktop.
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
Copy link
Author

nvh commented Oct 3, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment