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

68d48587fc806c2b35eb9ff0b7ad8115?s=128

Mike Zornek

January 14, 2016
Tweet

Transcript

  1. Consuming JSON in Swift Mike Zornek • January 14, 2016

  2. None
  3. None
  4. Consuming JSON in Swift

  5. What is JSON?

  6. [ { "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 } ]
  7. Consuming JSON in Swift Why do this talk?

  8. None
  9. https://github.com/zorn/JSONFunHouse

  10. Native Swift

  11. { "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/
  12. None
  13. 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") } }
  14. override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") {

    } else { presentError("Could not load data file.") } }
  15. 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.") } }
  16. 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.") } }
  17. 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.") } }
  18. 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.") } }
  19. Freddy A reusable framework for parsing JSON in Swift.

  20. 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.
  21. override func loadWeather() { if let data = getDataFromLocalJSONFileWithName("weather") {

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

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

    String init(name: String, biography: String) { self.name = name self.biography = biography } }
  27. 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") } }
  28. 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.") } }
  29. [ { "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 } ]
  30. 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 } }
  31. 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) } } }
  32. JSONFunHouse Demo

  33. Native SwiftyJSON Argo Freddy Type Safe Idiomatic Error Handling Speed

  34. Native SwiftyJSON Argo Freddy Type Safe D Idiomatic D Error

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

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

    D B D Error Handling D C B Speed C C C
  37. 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
  38. None
  39. 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
  40. 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/