$30 off During Our Annual Pro Sale. View Details »

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. Consuming JSON in Swift
    Mike Zornek • January 14, 2016

    View Slide

  2. View Slide

  3. View Slide

  4. Consuming JSON in Swift

    View Slide

  5. What is JSON?

    View Slide

  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
    }
    ]

    View Slide

  7. Consuming JSON in Swift
    Why do this talk?

    View Slide

  8. View Slide

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

    View Slide

  10. Native Swift

    View Slide

  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/

    View Slide

  12. View Slide

  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")
    }
    }

    View Slide

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

    View Slide

  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.")
    }
    }

    View Slide

  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.")
    }
    }

    View Slide

  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.")
    }
    }

    View Slide

  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.")
    }
    }

    View Slide

  19. Freddy
    A reusable framework for parsing JSON in Swift.

    View Slide

  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.

    View Slide

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

    View Slide

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

    View Slide

  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.")
    }
    }

    View Slide

  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.")
    }
    }

    View Slide

  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."
    },
    ...
    ]

    View Slide

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

    View Slide

  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")
    }
    }

    View Slide

  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.")
    }
    }

    View Slide

  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
    }
    ]

    View Slide

  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
    }
    }

    View Slide

  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)
    }
    }
    }

    View Slide

  32. JSONFunHouse Demo

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  38. View Slide

  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

    View Slide

  40. 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/

    View Slide