Using Alamofire and SwiftyJSON to retrieve some JSON is trivial:
Given JSON such as
{
"results": [
{
"id": "123",
"name": "Bob"
},
{
"id": "456",
"name": "Sally"
}
}
This function will work:
func loadSomeJSONData() {
Alamofire.request(.GET, "http://example.com/json/")
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
if let firstName = json["results"][0]["name"].string {
println("first name: \(firstName)") // firstName will equal "Bob"
}
}
}
All well and good. My problem arises when I need to load JSON from a paged API, that is, when the data is collected from multiple calls to an API endpoint, where the JSON looks more like:
{
"currentPage": "1",
"totalPages": "6"
"results": [
{
"id": "123",
"name": "Bob"
},
{
"id": "456",
"name": "Sally"
}
]
}
and then the next block would look like:
{
"currentPage": "2",
"totalPages": "6"
"results": [
{
"id": "789",
"name": "Fred"
},
{
"id": "012",
"name": "Jane"
}
]
}
In this case, I can recursively call a function to gather all the "pages" but I'm not sure how to put all the JSON fragments together properly:
func loadSomeJSONDataFromPagedEndPoint(page : Int = 1) {
Alamofire.request(.GET, "http://example.com/json/" + page)
.responseJSON { (_, _, data, _) in
let json = JSON(data!)
if let totalPages = json["totalPages"].description.toInt() {
if let currentPage = json["currentPage"].description.toInt() {
let pageOfJSON = json["results"]
// add pageOfJSON to allJSON somehow??
if currentPage < totalPages {
self.loadSomeJSONDataFromPagedEndPoint(page: currentPage+1)
} else {
// done loading all JSON pages
}
}
}
var allJSON
loadSomeJSONDataFromPagedEndPoint()
What I'd like to happen is to have the "results" portion of each JSON response eventually collected into a single array of objects (the { "id": "123", "name": "Bob"}
objects)
Bonus question: I'm not sure why I need to do json["totalPages"].description.toInt()
in order to get the value of totalPages
, there must be a better way?
You have several questions in here, so let's take them one at a time.
I can't tell from your post if you get valid JSON back for each page call or whether you need to put them altogether to complete the JSON. So let's walk through both cases.
You're already very close, you just need to tweak your JSON parsing a bit and store the results. Here's what this could look like.
class PagedDownloader {
var pagedResults = [AnyObject]()
func loadSomeJSONDataFromPagedEndPoint(page: Int) {
let request = Alamofire.request(.GET, "http://example.com/json/\(page)")
request.responseJSON { [weak self] _, _, jsonData, _ in
if let strongSelf = self {
let json = JSON(jsonData!)
let totalPages = json["totalPages"].stringValue.toInt()!
let currentPage = json["currentPage"].stringValue.toInt()!
let results = json["results"].arrayObject!
strongSelf.pagedResults += results
if currentPage < totalPages {
strongSelf.loadSomeJSONDataFromPagedEndPoint(currentPage + 1)
} else {
strongSelf.parsePagedResults()
}
}
}
}
func parsePagedResults() {
let json = JSON(pagedResults)
println(json)
}
}
You seem to know your way around SwiftyJSON so I'll let you handle the parsePagedResults
implementation.
Paging JSON
First off, you can't parse partial JSON, it just won't work. The NSJSONSerialization
will fail. This means that you can't use the responseJSON
serializer with paged JSON because data
will always be nil and error
will always be the json serialization error. Long story short, you need cache all your data until it's valid JSON, then you can parse.
Storing Paged JSON
If you're going to store it, this is what it could look like as a simple example without Alamofire getting in the mix.
class Pager {
let page1 = "{\"currentPage\":\"1\",\"totalPages\":\"3\",\"results\":[{\"id\":\"123\",\"name\":\"Bob\"},"
let page2 = "{\"id\":\"456\",\"name\":\"Sally\"},{\"id\":\"234\",\"name\":\"Christian\"},"
let page3 = "{\"id\":\"567\",\"name\":\"Jerry\"},{\"id\":\"345\",\"name\":\"John\"}]}"
let pages: [String]
let jsonData: NSMutableData
init() {
self.pages = [page1, page2, page3]
self.jsonData = NSMutableData()
}
func downloadPages() {
for (index, page) in enumerate(pages) {
jsonData.appendData(page.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)
}
let json = JSON(data: jsonData)
println(json)
if let totalPages = json["totalPages"].string?.toInt() {
println("Total Pages Value: \(totalPages)")
}
}
}
Your bonus question is answered at the end of that code chunk. You don't want to use
description
from SwiftyJSON, but instead thestring
optional cast and then optional chain into thetoInt
method.
Paging and Storing with Alamofire
Now that you have a simple example of how to write the JSON pages into data chunks, let's look at how that same approach could be used with the response
serializer in Alamofire.
class Downloader {
var jsonData = NSMutableData()
var totalPagesDownloaded = 0
let totalPagesToDownload = 6
func loadSomeJSONDataFromPagedEndPoint() {
for page in 1...self.totalPagesToDownload {
let request = Alamofire.request(.GET, "http://example.com/json/\(page)")
request.response { [weak self] _, _, data, _ in
if let strongSelf = self {
strongSelf.jsonData.appendData(data as NSData)
++strongSelf.totalPagesDownloaded
if strongSelf.totalPagesDownloaded == strongSelf.totalPagesToDownload {
strongSelf.parseJSONData()
}
}
}
}
}
func parseJSONData() {
let json = JSON(data: jsonData)
println(json)
}
}
Parsing the Resulting JSON with SwiftyJSON
Inside the parseJSONData
function, just use all the awesome features of SwiftyJSON to parse out the values you need.
I'm pretty sure that covers all your possible use cases and questions. Hope that helps!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With