Slide 1

Slide 1 text

Consuming JSON in Swift Mike Zornek • January 14, 2016

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

Consuming JSON in Swift

Slide 5

Slide 5 text

What is JSON?

Slide 6

Slide 6 text

[ { "title": "April Main Meeting", "date": "2016-04-14T18:30:00-0500", "rsvp_count": 2, "announced": false }, { "title": "February Main Meeting", "date": "2016-02-11T18:30:00-0500", "rsvp_count": 31, "announced": true }, { "title": "March Main Meeting", "date": null, "rsvp_count": 4, "announced": false } ]

Slide 7

Slide 7 text

Consuming JSON in Swift Why do this talk?

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

https://github.com/zorn/JSONFunHouse

Slide 10

Slide 10 text

Native Swift

Slide 11

Slide 11 text

{ "latitude": 39.9550363, "longitude": -75.167381, "timezone": "America/New_York", "offset": -5, "currently": { "time": 1452470553, "summary": "Clear", "icon": "clear-night", "nearestStormDistance": 24, "nearestStormBearing": 78, "precipIntensity": 0, "precipProbability": 0, "temperature": 46.8, ... }, ... ] https://developer.forecast.io/

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

import UIKit import Freddy class WeatherViewController: UIViewController { @IBOutlet var temperatureLabel: UILabel! @IBOutlet var summaryLabel: UILabel! var temperature: Double = -99.9 var summary = "Unknown" override func viewDidLoad() { super.viewDidLoad() loadWeather() } override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) temperatureLabel.text = String(format:"%.1f°", temperature) summaryLabel.text = summary } func loadWeather() { fatalError("loadWeather() should be implemented by a subclass") } }

Slide 14

Slide 14 text

override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") { } else { presentError("Could not load data file.") } }

Slide 15

Slide 15 text

override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") { do { if let weatherDictionary = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] { } else { presentError("Could not decode JSON data.") } } catch { presentError(error) } } else { presentError("Could not load data file.") } }

Slide 16

Slide 16 text

override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") { do { if let weatherDictionary = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] { if let currentlyDictionary = weatherDictionary["currently"] as? [String: AnyObject] { } else { presentError("Could not find `currently` key") } } else { presentError("Could not decode JSON data.") } } catch { presentError(error) } } else { presentError("Could not load data file.") } }

Slide 17

Slide 17 text

override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") { do { if let weatherDictionary = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] { if let currentlyDictionary = weatherDictionary["currently"] as? [String: AnyObject] { if let temperature = currentlyDictionary["temperature"] as? Double { if let summary = currentlyDictionary["summary"] as? String { self.temperature = temperature self.summary = summary } else { presentError("Could not find `summary` key") } } else { presentError("Could not find `temperature` key") } } else { presentError("Could not find `currently` key") } } else { presentError("Cound not decode JSON data.") } } catch { presentError(error) } } else { presentError("Could not load data file.") } }

Slide 18

Slide 18 text

override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") { do { if let weatherDictionary = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject], let currentlyDictionary = weatherDictionary["currently"] as? [String: AnyObject], let temperature = currentlyDictionary["temperature"] as? Double, let summary = currentlyDictionary["summary"] as? String { self.temperature = temperature self.summary = summary } else { presentError("Could not parse JSON.") } } catch { presentError(error) } } else { presentError("Could not load data file.") } }

Slide 19

Slide 19 text

Freddy A reusable framework for parsing JSON in Swift.

Slide 20

Slide 20 text

Design Goals 1. A type safe solution to parsing JSON in Swift. 2. An idiomatic solution that takes advantage of Swift’s features. 3. Great error information for mistakes that commonly occur while parsing JSON. 4. Speed.

Slide 21

Slide 21 text

override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") { } else { presentError("Could not load data file.") } }

Slide 22

Slide 22 text

override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") { do { } catch { presentError(error) } } else { presentError("Could not load data file.") } }

Slide 23

Slide 23 text

override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") { do { let json = try JSON(data: data) } catch { presentError(error) } } else { presentError("Could not load data file.") } }

Slide 24

Slide 24 text

override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") { do { let json = try JSON(data: data) temperature = try json.double("currently", "temperature") summary = try json.string("currently", "summary") } catch { presentError(error) } } else { presentError("Could not load data file.") } }

Slide 25

Slide 25 text

[ { "character": "Malcolm \"Mal\" Reynolds", "bio": "The owner of Serenity and former Independent sergeant in the pivotal Battle of Serenity Valley. Malcolm grew up on a ranch, and was raised by his mother and the ranch hands. In the Unification War, he fought for the Independent Army, the \"Browncoats\", as a platoon sergeant in the 57th Overlanders. He is cunning, a capable leader and a skilled fighter. Mal's main character drive is his will for independence. While he is not above petty theft, smuggling or even killing to maintain his free lifestyle, he is generally honest in his dealings with others, fiercely loyal to his crew and closely follows a personal moral code." }, { "character": "Zoe Alleyne Washburne", "bio": "Second-in-command onboard Serenity, a loyal wartime friend of Captain Reynolds, and the wife of Wash. Her surname during the Unification War was Alleyne. She was born and raised on a ship and served under Mal during the war as a corporal. Described by her husband as a \"warrior woman\", she is a capable fighter who keeps calm even in the most dangerous situations. She demonstrates an almost unconditional loyalty to Mal, the only exception noted being her marriage to Wash, which the captain claims was against his orders." }, ... ]

Slide 26

Slide 26 text

import Foundation struct FireflyCastMember { let name: String let biography: String init(name: String, biography: String) { self.name = name self.biography = biography } }

Slide 27

Slide 27 text

import Foundation struct FireflyCastMember { let name: String let biography: String init(name: String, biography: String) { self.name = name self.biography = biography } } extension FireflyCastMember: JSONDecodable { init(json value: JSON) throws { name = try value.string("character") biography = try value.string("bio") } }

Slide 28

Slide 28 text

private func loadCastData() { if let data = getDataFromFileURL(self.dataURL) { do { let json = try JSON(data: data) self.castList = try json.array().map(FireflyCastMember.init) } catch { presentError(error) } } else { presentError("Could not load data file.") } }

Slide 29

Slide 29 text

[ { "title": "April Main Meeting", "date": "2016-04-14T18:30:00-0500", "rsvp_count": 2, "announced": false }, { "title": "February Main Meeting", "date": "2016-02-11T18:30:00-0500", "rsvp_count": 31, "announced": true }, { "title": "March Main Meeting", "date": null, "rsvp_count": 4, "announced": false } ]

Slide 30

Slide 30 text

struct Meeting { let title: String let date: NSDate let rsvpCount: Int init(title: String, date: NSDate, rsvpCount: Int) { self.title = title self.date = date self.rsvpCount = rsvpCount } }

Slide 31

Slide 31 text

struct Meeting { enum Error: ErrorType { case DateParseErrorFromString(String) } ... } extension Meeting: JSONDecodable { init(json value: JSON) throws { title = try value.string("title") rsvpCount = try value.int("rsvp_count") let dateString = try value.string("date") let df = NSDateFormatter() // 2016-03-10T18:30:00-0500 df.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZ" if let parsedDate = df.dateFromString(dateString) { date = parsedDate } else { throw Error.DateParseErrorFromString(dateString) } } }

Slide 32

Slide 32 text

JSONFunHouse Demo

Slide 33

Slide 33 text

Native SwiftyJSON Argo Freddy Type Safe Idiomatic Error Handling Speed

Slide 34

Slide 34 text

Native SwiftyJSON Argo Freddy Type Safe D Idiomatic D Error Handling D Speed C

Slide 35

Slide 35 text

Native SwiftyJSON Argo Freddy Type Safe D B Idiomatic D B Error Handling D C Speed C C

Slide 36

Slide 36 text

Native SwiftyJSON Argo Freddy Type Safe D B B Idiomatic D B D Error Handling D C B Speed C C C

Slide 37

Slide 37 text

Native SwiftyJSON Argo Freddy Type Safe D B B A Idiomatic D B D A Error Handling D C B A Speed C C C A

Slide 38

Slide 38 text

No content

Slide 39

Slide 39 text

More JSON Thoughts • Freddy does have a new JSONEncodable protocol. • Build Services that are powered with DataSources. • See: Code Patterns https://vimeo.com/124773343 • Don’t be afraid to make download representations. • JSONAPI.org • Swagger

Slide 40

Slide 40 text

https://github.com/zorn/JSONFunHouse Please clone it and try it out. Feedback very welcome. Mike Zornek @zorn zorn@bignerdranch.com Keep an eye out for the official release of Freddy soon™. BNR’s Core Data Stack https://www.bignerdranch.com /blog/introducing-the-big-nerd-ranch-core-data-stack/