Slide 1

Slide 1 text

JSON Parsing in Swift Anat Gilboa

Slide 2

Slide 2 text

Inspiration

Slide 3

Slide 3 text

The Proposal: Argo Argo: - type-safe JSON decoding library that extracts models from JSON

Slide 4

Slide 4 text

Currying struct User { let id: Int let name: String let age: Int } static func create(id: Int)(name: String)(age: Int) -> User { return User(id: id, name: name, age: age) }

Slide 5

Slide 5 text

The Proposal: Result Result typealias JSONObject = [String:AnyObject] enum JSONError : ErrorType { case NoSuchKey(String) case TypeMismatch } func stringForKey(json: JSONObject, key: String) -> Result { guard let value = json[key] else { return .Failure(.NoSuchKey(key)) } if let value = value as? String { return .Success(value) } else { return .Failure(.TypeMismatch) } }

Slide 6

Slide 6 text

The Proposal: Result Result typealias JSONObject = [String:AnyObject] enum JSONError : ErrorType { case NoSuchKey(String) case TypeMismatch } func stringForKey(json: JSONObject, key: String) -> Result { guard let value = json[key] else { return .Failure(.NoSuchKey(key)) } if let value = value as? String { return .Success(value) } else { return .Failure(.TypeMismatch) } }

Slide 7

Slide 7 text

What's so special about parsing JSON in Swift?...

Slide 8

Slide 8 text

No Library, No Problem? struct User { let id: Int let name: String let age: Int }

Slide 9

Slide 9 text

if-let it up let user: User? if let user = json["user"] as? [String: String] { if let id = user["id"] { if let name = user["name"] { if let age = user["age"] { user = User(id, name: name, age: age) } } } }

Slide 10

Slide 10 text

guard? let user: User? guard let user = json["user"] as? [String: String] { if let id = user["id"], name = user["name"], age = user["age"] { user = User(id, name: name, age: age) } } else { return nil }

Slide 11

Slide 11 text

How ‘bout do-catch? 4 Good for many trys in sequence 4 Don't forget to mark the function that is throwing

Slide 12

Slide 12 text

The Goal 1. Look into different mapping libraries to see their interfaces 2. Take into account the simplicity of the respective APIs

Slide 13

Slide 13 text

The Subjects Alembic Argo Arrow CaesarParser Decodable Elevate Freddy Genome Gloss JSON mapper ObjectMapper SwiftyJSON Tailor Tyro Unbox

Slide 14

Slide 14 text

Example: SwiftyJSON import SwiftyJSON struct User { let id: Int let name: String let age: Int } let json = JSON(["id":2378, "name":"Jack", "age": 23]) if let name = json["name"].string { // Do a thing } else { print(json["name"].error) }

Slide 15

Slide 15 text

Example: SwiftyJSON public let ErrorUnsupportedType: Int = 999 public let ErrorIndexOutOfBounds: Int = 900 public let ErrorWrongType: Int = 901 public let ErrorNotExist: Int = 500 public let ErrorInvalidJSON: Int = 490

Slide 16

Slide 16 text

Example: Mapper import Mapper struct User { ... } extension User : Mappable { init(map: Mapper) throws { try id = map.from("id") try name = map.from("name") age = map.optionalFrom("age") } }

Slide 17

Slide 17 text

Example: Mapper public enum MapperError: ErrorType { case ConvertibleError(value: AnyObject?, type: Any.Type) case CustomError(field: String?, message: String) case InvalidRawValueError(field: String, value: Any, type: Any.Type) case MissingFieldError(field: String) case TypeMismatchError(field: String, value: AnyObject, type: Any.Type) }

Slide 18

Slide 18 text

Example: Freddy import Freddy struct User { ... } extension User: JSONDecodable { public init(json value: JSON) throws { id = try value.int("id") name = try value.string("name") age = try value.int("age") } }

Slide 19

Slide 19 text

Example: Freddy public enum Error: ErrorType { case IndexOutOfBounds(index: Swift.Int) case KeyNotFound(key: Swift.String) case UnexpectedSubscript(type: JSONPathType.Type) case ValueNotConvertible(value: JSON, to: Any.Type) }

Slide 20

Slide 20 text

Example: Decodable import Decodable struct User { ... } extension User: Decodable { static func decode(j: Any) throws -> User { return try User( id: j => "id", name: j => "name", age: j => "age" ) } }

Slide 21

Slide 21 text

Example: Decodable public enum DecodingError: ErrorProtocol, Equatable { case typeMismatch(expected: Any.Type, actual: Any.Type, Metadata) case missingKey(String, Metadata) case rawRepresentableInitializationError(rawValue: Any, Metadata) case other(ErrorProtocol, Metadata) }

Slide 22

Slide 22 text

The Findings

Slide 23

Slide 23 text

Protocol that throws 4 Decodable 4 Distillable 4 JSONDecodable 4 JSONConvertible

Slide 24

Slide 24 text

Forms of Decoding 4 custom operator 4 subscript

Slide 25

Slide 25 text

Custom Operators E.g. - <- - <-! - <-? - <--! - <--? - <~~ - =>

Slide 26

Slide 26 text

Subscript json["people"][0]["name"] or with a path json[path: "people", 0, "name"]

Slide 27

Slide 27 text

Error Handling 4 Result 4 Implementation of ErrorType

Slide 28

Slide 28 text

Conclusion

Slide 29

Slide 29 text

Conclusion 4 Type-safe 4 Generic-friendly 4 Error-informative

Slide 30

Slide 30 text

Example: Argo import Argo import Curry import Result struct User { let id: Int let name: String let age: Int? } extension User: Decodable { static func decode(j: JSON) -> Decoded { return curry(User.init) <^> j <| "id" <*> j <| "name" <*> j <|? "age" } }

Slide 31

Slide 31 text

Further Reading http://bit.ly/tryJSON 1. Efficient JSON in Swift with Functional Concepts and Generics 2. Real World JSON Parsing with Swift 3. Parsing Embedded JSON and Arrays in Swift (H/T Tony DiPasquale)

Slide 32

Slide 32 text

Thanks! 4 Community members: Matt Diephouse, Soroush Khanlou, Matt Bischoff 4 Amex friends 4 Your awesome JSON decoding libraries!

Slide 33

Slide 33 text

@anat_gilboa