This post looks at some technical details regarding the use of Alamofire and SwiftyJSON for parsing data from a RESTful backend built with Spring Data REST and MongoDB.

Update March 27, 2018: If you are looking for content on integrating with a REST API starting with Swift 4, I have a newer post with a complete demo app, Swift App Integration With A REST API – Case Study and Demo App, that uses the new Swift 4 Codable protocol for easy JSON parsing.

The backend referenced in this post was based on Spring Boot, and I tested it locally on my Mac and also deployed it to Heroku.

This is the second Spring application I’ve deployed to Heroku, and that part of the process was again very slick.

If you are only interested in the Swift/iOS side and parsing of JSON from the RESTful server, you can jump ahead to that section.

Note: I’ve used Parse as a backend for two apps. I found it a pleasure to work with, and with Parse it was easy to get a backend up and running quick. However, the more of your server side that is dependent on a third party, the less control you have and we’re seeing now what can happen as thousands of companies and app developers must now replace their Parse backend solutions with something else given the recent announcement of Parse winding down in early 2017. The timing seemed optimum to post about one of the many alternatives to using Parse. In this case, I’m covering the option to write your own backend and deploy it to a PaaS (Platform as a Service) such as Heroku. Jamieson Quave wrote an interesting article on the subject of building your own backend in order to maintain control and stability.

Spring Data REST and MongoDB Backend

The Spring Data REST backend stores data in a MongoDB instance. With the backend deployed to Heroku, integration with MongoDB was achieved via the Heroku MongoLab add-on. One of the interesting things about using MongoDB is that Parse has committed to providing a migration tool to move data from Parse to an instance of MongoDB.

Spring Boot makes it easy to create stand-alone, production-grade Spring based applications, and I leveraged it for kickstarting this project. Spring Boot’s convention over configuration gets a project up and running as quickly as possible with minimum fuss.

One aspect about Spring Data REST is that by default, it is fully Hypertext Application Language (HAL) compliant, which means that it adheres to the Richardson Maturity Model Level 3, and that has some implications on parsing of the JSON data.

With HAL the JSON returned will have properties, links, and embedded resources. A basic block diagram illustrates it.

From a practical standpoint, this means that the client receives more than just plain objects in JSON format. Suppose we are storing quotes on our backend. The Quote class I’ve defined in Java is below.

package wisdom; import org.springframework.data.annotation.Id; import java.util.Date; public class Quote { @Id private String id; private String text; private String author; private boolean local = false; private Date createdAt = new Date(); private Date activeAt = new Date(); public String getId() { return id; } public void setId(String id) { this.id = id; } public String getText() { return text; } public void setText(String text) { this.text = text; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public boolean isLocal() { return this.local; } public void setLocal(boolean active) { this.local = local; } public void setCreatedAt(Date date) { this.createdAt = (Date) date.clone(); } public Date getCreatedAt() { return (Date) this.createdAt.clone(); } public void setActiveAt(Date date) { this.activeAt = (Date) date.clone(); } public Date getActiveAt() { return (Date) this.activeAt.clone(); } }

Using Postman to test the RESTful API, here is an example of the JSON you would retrieve with Spring Data REST out of the box, if you do a GET for all quotes.

{ "_embedded": { "quotes": [ { "id": "5691466cef863647f8c68760", "text": "Someone is sitting in the shade today because someone planted a tree a long time ago.", "author": "Warren Buffet", "local": false, "createdAt": "2016-01-09T17:42:04.912+0000", "activeAt": "2016-01-09T17:42:04.912+0000", "_links": { "self": { "href": "http://localhost:8080/quotes/5691466cef863647f8c68760" }, "quote": { "href": "http://localhost:8080/quotes/5691466cef863647f8c68760" } } }, { "id": "56914842ef8665b64f2e2726", "text": "Motivation is what gets you started. Habit is what keeps you going.", "author": "Jim Rohn", "local": false, "createdAt": "2016-01-09T17:49:54.116+0000", "activeAt": "2016-01-09T17:49:54.116+0000", "_links": { "self": { "href": "http://localhost:8080/quotes/56914842ef8665b64f2e2726" }, "quote": { "href": "http://localhost:8080/quotes/56914842ef8665b64f2e2726" } } }, { "id": "56914e95ef86c2376f6db351", "text": "If you want something new you need to stop doing something old.", "author": "Peter Drucker", "local": false, "createdAt": "2016-01-09T18:16:53.141+0000", "activeAt": "2016-01-09T18:16:53.141+0000", "_links": { "self": { "href": "http://localhost:8080/quotes/56914e95ef86c2376f6db351" }, "quote": { "href": "http://localhost:8080/quotes/56914e95ef86c2376f6db351" } } } ] }, "_links": { "self": { "href": "http://localhost:8080/quotes" }, "profile": { "href": "http://localhost:8080/profile/quotes" }, "search": { "href": "http://localhost:8080/quotes/search" } }, "page": { "size": 20, "totalElements": 3, "totalPages": 1, "number": 0 } }

You see both the _embedded and _links reserved properties in the JSON. For the purposes of this discussion, lets ignore _links that are not in the embedded resources.

The key point I wanted to illustrate here is that when using Alamofire and SwiftyJSON (or any other client side REST handling frameworks), you have to handle the _embedded property. There is an alternative, which is to tell the Spring configuration that you don’t want HAL compliance, and this can be done by adding a configuration, and specifically a RepositoryRestConfigurerAdapter that specifies not to return HAL+JSON, but rather just JSON.

@Configuration class SpringDataRestConfig { @Bean public RepositoryRestConfigurer repositoryRestConfigurer() { return new RepositoryRestConfigurerAdapter() { @Override public void configureRepositoryRestConfiguration( RepositoryRestConfiguration config) { // Expose id attribute over the REST interface config.exposeIdsFor(Quote.class); // Specify JSON instead of default HAL+JSON config.setDefaultMediaType(MediaType.APPLICATION_JSON); } }; } }

In the code above, I’ve also specified that the id attribute of the Quote object shall be exposed over the REST interface, otherwise the default behaviour is not to expose it, given that technically the URI is the unique resource identifier. However, in this case I want to be able to do a GET for a single Quote using the id, so I’ve enabled exposing this attribute, which I had already configured for the JSON shown above.

Running a GET request for retrieving all quotes, using localhost:8080/quotes, assuming we’ve specified the MediaType.APPLICATION_JSON, would return the JSON below, where _embedded is no longer present and all quotes are directly within the content property.

{ "links": [ { "rel": "self", "href": "http://localhost:8080/quotes" }, { "rel": "profile", "href": "http://localhost:8080/profile/quotes" }, { "rel": "search", "href": "http://localhost:8080/quotes/search" } ], "content": [ { "id": "5691466cef863647f8c68760", "text": "Someone is sitting in the shade today because someone planted a tree a long time ago.", "author": "Warren Buffet", "local": false, "createdAt": "2016-01-09T17:42:04.912+0000", "activeAt": "2016-01-09T17:42:04.912+0000", "content": [], "links": [ { "rel": "self", "href": "http://localhost:8080/quotes/5691466cef863647f8c68760" }, { "rel": "quote", "href": "http://localhost:8080/quotes/5691466cef863647f8c68760" } ] }, { "id": "56914842ef8665b64f2e2726", "text": "Motivation is what gets you started. Habit is what keeps you going.", "author": "Jim Rohn", "local": false, "createdAt": "2016-01-09T17:49:54.116+0000", "activeAt": "2016-01-09T17:49:54.116+0000", "content": [], "links": [ { "rel": "self", "href": "http://localhost:8080/quotes/56914842ef8665b64f2e2726" }, { "rel": "quote", "href": "http://localhost:8080/quotes/56914842ef8665b64f2e2726" } ] }, { "id": "56914e95ef86c2376f6db351", "text": "If you want something new you need to stop doing something old.", "author": "Peter Drucker", "local": false, "createdAt": "2016-01-09T18:16:53.141+0000", "activeAt": "2016-01-09T18:16:53.141+0000", "content": [], "links": [ { "rel": "self", "href": "http://localhost:8080/quotes/56914e95ef86c2376f6db351" }, { "rel": "quote", "href": "http://localhost:8080/quotes/56914e95ef86c2376f6db351" } ] } ], "page": { "size": 20, "totalElements": 3, "totalPages": 1, "number": 0 } }

I’ve shown the JSON immediately above just as a comparison for what you can do by altering the Spring configuration which defaults to HAL and HATEOAS.

The sample JSON I’ve shown is from running against the backend deployed locally. Deploying to Heroku is actually very simple as well. The steps are described here.

Once you’ve created the project on Heroku, each time you make changes you simply need to run the command below, from the terminal when logged into Heroku, to push the new version and run it.

git push heroku master

I used Gradle as the build tool for the backend. For reference, on GitHub you can take a look at the complete Spring Data REST backend project.

I’ll add a couple of other details in case you want to try creating a Spring Data REST backend. First, you can check this excellent tutorial for a step by step guide in how to do it.

The second thing worth noting is that you need to create a procfile for Heroku. To facilitate having a procfile that contains the MongoDB bootstrap data for the Heroku (MongoLab) instance of MongoDB, you should have a procfile that contains the following.

web: java -Dserver.port=$PORT $JAVA_OPTS -jar app.jar --spring.profiles.active=heroku

This procfile specifies what profile to use when running on Heroku via the spring.profiles.active value. This allows you to use two different properties files, one for development, and one for deployment to Heroku.

When the application is loading, a file named application-{profile}.properties will only be loaded when the matching profile is active, so in the case above application-heroku.properties will be loaded only on Heroku based on the procfile. Locally, the default application.properties file would be loaded.

The Heroku profile-specific configuration will override the default configuration in application.properties, and connect to the right MongoDB instance specified in the file. You can see these two files in my Spring Data REST project, config subdirectory.

The regular application.properties file contains the following.

spring.data.mongodb.database=wisdomdb

The wisdomdb is my local Mongo database name.

The application-heroku.properties file contains the following.

spring.data.mongodb.uri=${MONGOLAB_URI} spring.data.mongodb.database=heroku_123xyz

The value heroku_123xyz is a fictitious value, but when you deploy to Heroku and use the MongoLab add-on, you receive a database name that you would put in this file. The MONGOLAB_URI property is available to your application on Heroku to provide it with the necessary URI to access the MongoDB instance from MongoLab, so you need not worry about specifying anything beyond what is shown above.

You may want the actual credentials (username and password) for the MongoDB instance in order to use a tool such as Robomongo to browse and manage the database. To acquire the credentials, you need to login to Heroku, and issue the following command.

heroku config | grep MONGOLAB_URI

This will print out all the data you need, in the following format, but with values filled in for the dbuser and dbpassword.

mongodb://<dbuser>:<dbpassword>@ds039185.mongolab.com:39185/heroku_123xyz

Now that you have the credentials and database name, you can connect to the database with a database browser tool, so I’ll illustrate what to do with Robomongo.

In Robomongo, click on Connect in the toolbar, or File >> Connect. You will see a window like this one.

Provide a name for the connection, and the host/port which you see in the MongoLab console when you requested the MONGOLAB_URI, or on the MongoLab dashboard you access from the Heroku dashboard.

On the Authentication tab, you need to check Perform authentication.

Enter the Database name, User Name, and Password, then click Save. You can then click on the connection you just created from the list, and click Connect. If there are any issues connecting, be sure you have the latest release of Robomongo and double check the credentials you entered.

Alamofire and SwiftyJSON

Assuming we leave the backend HAL compliant as it is out of the box, it is easy to request quotes using Alamofire and SwiftyJSON. To start, I’ve defined a Quote class in Swift that mirrors the class created in Java for the backend.

import Foundation class Quote { var text: String var author: String var createdAt: NSDate var activeAt: NSDate var id: String var local: Bool init(id:String, text:String, author:String, createdAt:NSDate, activeAt:NSDate,local:Bool) { self.id = id self.text = text self.author = author self.createdAt = createdAt self.activeAt = activeAt self.local = local } }

In a ViewController, I have a method that requests quotes from a QuoteStore, as follows.

func fetchRemoteQuotes() { var remoteQuotesArray = [Quote]() QuoteStore.sharedInstance.fetchRemoteQuotes({ (responseObject:[Quote]?, error:NSError?) in if let issue = error { print(issue.description) } else { if let fetchedQuotes = responseObject { remoteQuotesArray = fetchedQuotes // controller would do something with quotes - I'll just print count print("**Fetched quotes count**: \(remoteQuotesArray.count)") } } }) }

You’ll notice that the fetchRemoteQuotes method takes a function with two parameters, optional array of Quote instances, and optional NSError, returning void. I’ll use a typealias to define the closure.

typealias RemoteQuotesResponse = ([Quote]?, NSError?) -> Void

The fetchRemoteQuotes method in the QuoteStore is written as follows.

func fetchRemoteQuotes(onCompletion: RemoteQuotesResponse) -> Void { Alamofire.request(.GET, REMOTE_URL).validate().responseString { response in var allQuotes = [Quote]() switch response.result { case .Success: guard let responseValue = response.result.value else { onCompletion(allQuotes, nil) return } guard let dataFromResponse = responseValue.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false) else { onCompletion(allQuotes, nil) return } let json = JSON(data: dataFromResponse) allQuotes = Quote.buildQuotes(json) onCompletion(allQuotes, nil) case .Failure(let error): onCompletion(nil, error) } } }

The onCompletion parameter is the RemoteQuotesResponse, or in other words the function with two parameters that returns void.

([Quote]?, NSError?) -> Void

When the data is retrieved successfully, onCompletion is how the callback to the controller is made with either an array of Quotes or an error. The way to think of it is that the fetchRemoteQuotes method, in the success case, is calling the function that the callee (the controller) has provided, and for that function the associated arguments are provided, namely the array of quotes and the error. They are both optional arguments so only one value is provided depending on success or failure, and the other is nil.

Alamofire and SwiftyJSON are used together to interact with the RESTful backend by making a GET request to REMOTE_URI.

I’ve run this against the backend deployed to Heroku, and deployed locally on my Mac, with the definitions for the URI as one of the two below.

private var REMOTE_URI = "https://generated-name.herokuapp.com/quotes" private var REMOTE_URI = "https://localhost:8080/quotes"

Where I’ve shown generated-name, this isn’t the name Heroku generated for me, so I’m just illustrating that there will be some random name generated by Heroku, which can be updated.

I won’t repeat too much of the content you can find in the documentation for Alamofire and SwiftyJSON. The main concept is that you either have a success or failure from the request. If it is a failure, the onCompletion callback is made with an error. If it is success, then the response is checked to make sure there is a value, which is then transformed into NSData, and finally a JSON instance.

I added a buildQuotes method to the Quote class to take the instance of JSON and build an array of Quote instances for the callback.

I also added a createQuote method to the Quote class to create single instances of each Quote.

// Accepts JSON and returns array of Quote instances class func buildQuotes(json:JSON) -> [Quote] { var tempItems = [Quote?]() // Option 1: Get array of quotes guard let array = json["_embedded"]["quotes"].arrayObject else { return [Quote]() } for dataObject : AnyObject in array { guard let dict = dataObject as? NSDictionary else { continue } tempItems.append(createQuote(dict)) } // Remote nil quotes return tempItems.flatMap { $0 } } class func createQuote(dict:NSDictionary) -> Quote? { if let text = dict["text"] as? String, author = dict["author"] as? String, //tempCreate = dict["createdAt"] as? String, createdAt = Formatter.jsonDateTimeFormatter.dateFromStringOptional(dict["activeAt"] as? String), activeAt = Formatter.jsonDateTimeFormatter.dateFromStringOptional(dict["activeAt"] as? String), local = dict["local"]?.boolValue, id = dict["id"] as? String { return Quote(id: id, text:text, author:author, createdAt:createdAt, activeAt:activeAt,local:local) } else { return nil } }

I haven’t shown any error handling for invalid or missing values, so depending on your requirements you might want an enumeration of error cases to be factored in.

You can use SwiftyJSON in different ways as indicated in the documentation. I’ve shown two options for how you can get at the actual Quote content of the JSON.

In the code above, where I’ve commented Option 1, it illustrates getting the array in one step. This could be done using two separate steps as follows:

// Option 2 - Get _embedded dictionary first, then array of quotes guard let quotesDict = json["_embedded"].dictionaryObject else { return [Quote]() } guard let array = quotesDict["quotes"] as? NSArray else { return [Quote]() }

Option 2 is first getting the _embedded dictionary, and then the array for the quotes key in a second step. I tried both ways while experimenting and am showing the 2nd option just for illustration.

The array is iterated and each element is an NSDictionary of the key/value pairs for a given Quote. For each dictionary, the createQuote method is used to extract the data. If anything is missing, nil is returned and appended to tempItems, otherwise the created Quote is returned and appended.

Finally, flatMap is used to remove nil values from the array that buildQuotes returns.

I also have a Formatter class to handle dates and an extension for NSDateFormatter.

import Foundation import SwiftyJSON class Formatter { private static var internalJsonDateTimeFormatter: NSDateFormatter? static var jsonDateTimeFormatter: NSDateFormatter { if (internalJsonDateTimeFormatter == nil) { internalJsonDateTimeFormatter = NSDateFormatter() internalJsonDateTimeFormatter!.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'SSSZZ" } return internalJsonDateTimeFormatter! } } extension NSDateFormatter { func dateFromStringOptional(string:String?) -> NSDate? { guard let value = string else { return nil } return self.dateFromString(value) } }

It took a few tries to get the dateFormat correct for the jsonDateTimeFormatter to correctly deal with the String returned from the backend.

I added the extension to NSDateFormatter so that I could pass a nil value for the date field String values if they were missing from the dictionary, and thus could use multiple optionals for the if let to check for all fields before creating an instance of Quote.

I hope this helps you use Alamofire and SwiftyJSON for working with RESTful backends, and furthermore, maybe you’d like to implement your own backend using Spring Data REST and MongoDB.

I also wrote a tutorial on developing a backend using Node.js, Express, MongoDB, and Mongoose, which you can find here on my website.

Just for some added excitement, I replicated the Node.js backend tutorial to create a Node.js backend for the quotes. The Node.js backend is integrated with the same MongoDB instance I ran locally for the Spring based backend, but I’ve only tested it with Postman and did not go further. I’d have to tweak the JSON parsing to integrate it with my test app because the content is plain JSON. Here is the JSON below.

[ { "_id": "5691466cef863647f8c68760", "_class": "wisdom.Quote", "text": "Someone is sitting in the shade today because someone planted a tree a long time ago.", "author": "Warren Buffet", "activeAt": "2016-01-09T17:42:04.912Z", "createdAt": "2016-01-09T17:42:04.912Z", "local": false }, { "_id": "56914842ef8665b64f2e2726", "_class": "wisdom.Quote", "text": "Motivation is what gets you started. Habit is what keeps you going.", "author": "Jim Rohn", "activeAt": "2016-01-09T17:49:54.116Z", "createdAt": "2016-01-09T17:49:54.116Z", "local": false }, { "_id": "56914e95ef86c2376f6db351", "_class": "wisdom.Quote", "text": "If you want something new you need to stop doing something old.", "author": "Peter Drucker", "activeAt": "2016-01-09T18:16:53.141Z", "createdAt": "2016-01-09T18:16:53.141Z", "local": false } ]

As Phil Collins has said, “In learning you will teach, and in teaching you will learn.”

On that note, if you know some alternative techniques I’ve not used on the iOS or RESTful server side of things in this post, I’d welcome your feedback to learn more myself and to help other readers as well.