-
Notifications
You must be signed in to change notification settings - Fork 119
Further Examples
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.
{
"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"
}
}
]
}
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 Technician
s 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`
}
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`
}
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.
Created by Big Nerd Ranch 2015