Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Consuming JSON in Swift

Consuming JSON in Swift

JSON is a fundamental internet format and if you are building iOS apps the chance you need to download and consume JSON files is extremely high. Additionally, with the introduction of the statically typed Swift language it's been a little more difficult to work with JSON properly. This talk will cover what JSON is, how one can work with it naturally in Swift, the limitations of doing so, a review of a few popular third party solutions and the introduction of a new JSON tool about to be open sourced by Big Nerd Ranch.

Video: https://vimeo.com/152112429
Code: https://github.com/zorn/JSONFunHouse

Mike Zornek

January 14, 2016
Tweet

More Decks by Mike Zornek

Other Decks in Programming

Transcript

  1. [ { "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 } ]
  2. { "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/
  3. 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") } }
  4. 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.") } }
  5. 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.") } }
  6. 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.") } }
  7. 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.") } }
  8. 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.
  9. override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") {

    do { } catch { presentError(error) } } else { presentError("Could not load data file.") } }
  10. 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.") } }
  11. 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.") } }
  12. [ { "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." }, ... ]
  13. import Foundation struct FireflyCastMember { let name: String let biography:

    String init(name: String, biography: String) { self.name = name self.biography = biography } }
  14. 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") } }
  15. 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.") } }
  16. [ { "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 } ]
  17. 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 } }
  18. 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) } } }
  19. Native SwiftyJSON Argo Freddy Type Safe D B B Idiomatic

    D B D Error Handling D C B Speed C C C
  20. 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
  21. 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
  22. https://github.com/zorn/JSONFunHouse Please clone it and try it out. Feedback very

    welcome. Mike Zornek @zorn [email protected] 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/