Skip to content
This repository has been archived by the owner on Sep 19, 2018. It is now read-only.

Further Examples

mdmathias edited this page Sep 15, 2016 · 8 revisions

This page provides some further examples of parsing JSON. It defines some sample JSON, and demonstrates some options to show how you may access the data you need.

Make sure to read the entire page to understand your options and our suggestions.

Sample JSON

{
  "data": [
    {
      "id": "1",
      "type": "technicians",
      "links": {
        "self": "https://myawesomesite.com/api/technicians/1"
      },
      "attributes": {
        "first-name": "John",
        "last-name": "Smith",
        "phone": "111-111-1111",
        "full-name": "John Smith"
      }
    },
    {
      "id": "2",
      "type": "technicians",
      "links": {
        "self": "https://myawesomesite.com/api/technicians/2"
      },
      "attributes": {
        "first-name": "Abbie",
        "last-name": "Mather",
        "phone": "222-222-2222",
        "full-name": "Abbie Mather"
      }
    }
  ]
}

"attributes"

Imagine that you want to make instances of a Technician type from the "attributes" key in the above JSON. You could accomplish this like so:

struct Technician {
    let name: String
    let phone: String
}

extension Technician: JSONDecodable {
    init(json: JSON) throws {
        name = try json.getString(at: "full-name")
        phone = try json.getString(at: "phone")
    }
}

do {
    // Assuming `json` has the JSON data
    let attrs = try json.getArray(at: "data").map { try JSON($0.getDictionary(at: "attributes")) }
    let technicians = try attrs.map(Technician.init)
    // Do something with `technicians`
} catch {
   // Do something with `error`
}

First, we call getArray(at:) to get the [JSON] value from the "data" key. Second, we call map(_:) to create an array of JSON dictionaries corresponding to each value "attributes" keys. We have to wrap up this code $0.getDictionary(at: "attributes") within a JSON instance in order to end up with a [JSON]. Third, and last, we can now map from this array of attributes dictionaries to an array of Technicians by calling try attrs.map(Technician.init), which calls the JSONDecodable initializer for each element within attrs.

Another option would be to map from the "attributes" dictionaries more directly.

do {
    // Assuming `json` has the JSON data
    let json = try JSON(data: data!)
    let technicians = try json.getArray(at: "data").map { jsonDict -> Technician in
        let technicianDictionary = try JSON(jsonDict.getDictionary(at: "attributes"))
        return try Technician(json: technicianDictionary)
    }
    // Do something with `technicians`
} catch {
    // Do something with `error`
}

Adding "links"

Now imagine that the model needs to have the technicians' link on www.myawesomesite.com. This data is located at a completely different point in the JSON's structure. While that does present some challenge, here is how you can get around that.

First, you need to add "links" to the Technician model.

struct Technician {
    let name: String
    let phone: String
    let link: String
}

extension Technician: JSONDecodable {
    init(json: JSON) throws {
        name = try json.getString(at: "attributes", "full-name")
        phone = try json.getString(at: "attributes", "phone")
        link = try json.getString(at: "links", "self")
    }
}

Now, the trick is to get the "links" dictionaries and the "attributes" dictionaries and combine them into a single JSON.Dictionary that we can send to the init(json:) initializer on Technician. The solution above describes the correct paths inside the JSON directly within Technician's conformance to JSONDecodable.

do {
    let json = try JSON(data: data!)
    let technicians = try json.decodedArray(at: "data", type: Technician.self)
    // Do something with `technicians`
} catch {
    // Do something with `error`
}

Revisiting "attributes"

Recall the first example of parsing the JSON into Technician instances.

do {
    // Assuming `json` has the JSON data
    let json = try JSON(data: data!)
    let technicians = try json.getArray(at: "data").map { jsonDict -> Technician in
        let technicianDictionary = try JSON(jsonDict.getDictionary(at: "attributes"))
        return try Technician(json: technicianDictionary)
    }
    // Do something with `technicians`
} catch {
    // Do something with `error`
}

This solution was predicated on sending the dictionary associated with the "attributes" key to the init(json:) initializer on Technician (notice that this initializer omits the "attributes" key).

Given what we have just seen, we can streamline this solution by pushing that path into the JSONDecodable initializer (as we saw in the section on "links").

extension Technician: JSONDecodable {
    init(json: JSON) throws {
        name = try json.getString(at: "attributes", "full-name")
        phone = try json.getString(at: "attributes", "phone")
        link = try json.getString(at: "links", "self")
    }
}

Once we have done that, we can revise our solution to use decodedArray(at:type:).

do {
    let json = try JSON(data: data!)
    let technicians = try decodedArray(at: "data", type: Technician.self)
    // Do something with `technicians`
} catch {
    // Do something with `error`
}

This works because the "data" key's value is an array of dictionaries containing data that our Technician model needs to be instantiated. The trick here is that the responsibility for defining the various paths to the necessary data is given to the Technician's initializer. This helps to keep our logic cleaner and easier to follow.