Alamofire is a great Swift library developed by the creator of AFNetworking @mattt. The purpose of this gist is to explain how to use the built-in power of Alamofire to serialize your JSON. In this example we will be serializing a simple blog API. First we will start with serializing a single JSON object and add complexity as we go along.
This is the first JSON object that we will be serializing.
// GET posts/1.json
{
"id": 1,
"title": "Post #1",
"body": "My first blog post"
}
In order to use the built-in JSON Serialization of Alamofire we need to extend the default functionality of the Alamofire.Request object with this snippet. This snippet is taken from the Alamofire documentation called Generic Response Object Serialization.
// ResponseObjectSerializable.swift
@objc public protocol ResponseObjectSerializable {
init?(response: NSHTTPURLResponse, representation: AnyObject)
}
extension Alamofire.Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, T?, NSError?) -> Void) -> Self {
let serializer: Serializer = { (request, response, data) in
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
if response != nil && JSON != nil {
return (T(response: response!, representation: JSON!), nil)
} else {
return (nil, serializationError)
}
}
return response(serializer: serializer, completionHandler: { (request, response, object, error) in
completionHandler(request, response, object as? T, error)
})
}
}
Now that we can call the responseObject on Alamofire.Request object, we need to implement the ResponseObjectSerializable protocol. This protocol tells Alamofire how to map JSON values in the representation object to instance variables in the Post model. Make sure you use the keyword final and immutable variables with let.
// Post.swift
final class Post: ResponseObjectSerializable {
let id: Int
let title: String
let body: String
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.id = representation.valueForKeyPath("id") as Int
self.title = representation.valueForKeyPath("title") as String
self.body = representation.valueForKeyPath("body") as String
}
}
You can now put all of the pieces together and make a request that implements the responseObject method and serializes your Post object.
Alamofire.request(.GET, "http://example.com/posts/1.json")
.responseObject { (_, _, post: Post?, _) in
println(post)
}
So that wasn’t so bad, but building a great app requires pulling in a lot more than just one object. Most of the time you will need to pull in a collection of objects. Here is an example JSON collection of post objects that we will now serialize.
// GET /posts.json
[
{
"id": 1,
"title": "Post #1",
"body": "My first blog post"
},
{
"id": 2,
"title": "Post #2",
"body": "My second blog post"
},
{
"id": 3,
"title": "Post #3",
"body": "My third blog post"
}
]
To serialize a collection of objects we will need to extend the Alamofire.Request object again in order to add the responseCollection method to our network requests. Once again I will paste in the code from the documentation under Generic Response Object Serialization.
// ResponseCollectionSerializable.swift
@objc public protocol ResponseCollectionSerializable {
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}
extension Alamofire.Request {
public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, [T]?, NSError?) -> Void) -> Self {
let serializer: Serializer = { (request, response, data) in
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
if response != nil && JSON != nil {
return (T.collection(response: response!, representation: JSON!), nil)
} else {
return (nil, serializationError)
}
}
return response(serializer: serializer, completionHandler: { (request, response, object, error) in
completionHandler(request, response, object as? [T], error)
})
}
}
We have now added the responseCollection to the Alamofire.Request object. Next we need to go and implement the ResponseCollectionSerializable protocol. We will build from our existing Post.swift file.
// Post.swift
// Note: we are now implemented the ResponseCollectionSerialiable protocol
final class Post: ResponseObjectSerializable, ResponseCollectionSerializable {
let id: Int
let title: String
let body: String
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.id = representation.valueForKeyPath("id") as Int
self.title = representation.valueForKeyPath("title") as String
self.body = representation.valueForKeyPath("body") as String
}
// This is where the magic happens
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Post] {
var postArray = representation as [AnyObject]
// using the map function we are able to instantiate Post while reusing our init? method above
return postArray.map({ Post(reponse:response, representation: $0) })
}
}
Now when we make the Alamofire request we can call the responseCollection method. Then Alamofire will run our collection method which loops through our posts array and calls the init? method on each. It will then return an array of Post objects.
Alamofire.request(.GET, "http://blog.com/posts.json")
.responseCollection { (_, _, posts: [Post]?, error) in
println(posts)
}
Being able to serialize collections gives an app more power, but still a JSON collection will most likely have nested objects and collections inside of it. Alamofire supports this as well, but requires more work in our model layer. Here is the JSON that we want to serialize. Notice that it has an author object inside of it and an array of comments.
// GET /posts/1.json
{
"id": 1,
"title": "Alamofire JSON Serialization",
"body": "All about serialization in Alamofire...",
"author": {
"id", 1,
"full_name": "Jeff Potter",
"user_name": "jpotts18"
},
"comments": [
{
"id": 1,
"body": "Thanks for the help Jeff, this saved me hours"
},
{
"id": 2,
"body": "Your welcome. I am happy to help!"
}
]
}
Our JSON now has nested objects inside of itself. We will now serialize the author JSON object. To start off we will add our Author model, then implement the ResponseObjectSerializable, and finally add our Author object into the Post object.
// Author.swift
// Implement the protocol
final class Author: ResponseObjectSerializable {
let id: Int
let fullName: String
let userName: String
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
// map the values to the instance
self.id = representation.valueForKeyPath("id") as Int
self.fullName = representation.valueForKeyPath("full_name") as String
self.userName = representation.valueForKeyPath("user_name") as String
}
}
Now that the Author object can be serialized we can add it to our Post.init? method.
// Post.swift
final class Post: ResponseObjectSerializable, ResponseCollectionSerializable {
let id: Int
let title: String
let body: String
let author: Author // Adding the author here
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.id = representation.valueForKeyPath("id") as Int
self.title = representation.valueForKeyPath("title") as String
self.body = representation.valueForKeyPath("body") as String
// We will instantiate the Author object using Author.init?
self.author = Author(response:response, representation: representation.valueForKeyPath("author")!)!
}
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Post] {
var postArray = representation as [AnyObject]
return postArray.map({ Post(reponse:response, representation: $0) })
}
}
Now that we know how to do a nested Author object inside of the Post, we can move on to serializing the comments collection. First we will need to add the Comment model and implement the init? and collection methods.
// Comment.swift
final class Comment: ResponseObjectSerializable, ResponseCollectionSerializable {
let id: Int
let body: String
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.id = representation.valueForKeyPath("id") as Int
self.body = representation.valueForKeyPath("body") as String
}
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Comment] {
var commentArray = representation as [AnyObject]
return commentArray.map({ Comment(reponse:response, representation: $0) })
}
}
Now we need to go and add our Comments array into our Post model. In our init? method we will pluck the comments array from the representation and pass it to Comment.collection for serialization.
// Post.swift
final class Post: ResponseObjectSerializable, ResponseCollectionSerializable {
let id: Int
let title: String
let body: String
let author: Author // nested object
let comments: [Comment] // nested array
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.id = representation.valueForKeyPath("id") as Int
self.title = representation.valueForKeyPath("title") as String
self.body = representation.valueForKeyPath("body") as String
self.author = Author(response:response, representation: representation.valueForKeyPath("author")!)!
// Pluck the comments array from the representation and pass it to Comment.collection for serialization.
self.comments = Comment.collection(response:response, representation: representation.valueForKeyPath("comments")!)!
}
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Post] {
var postArray = representation as [AnyObject]
return postArray.map({ Post(reponse:response, representation: $0) })
}
}
We are now ready to make our network request using Alamofire and let it handle all of the nested serialization and instantiation behind the scenes. Here is what the request will look like.
Alamofire.request(.GET, "http://blog.com/posts/1.json")
.responseCollection { (_, _, nestedPost: Post?, error) in
println(nestedPost)
}
Alamofire provides a simple way to map JSON representations to instance objects. By implementing the ResponseObjectSerializable and ResponseCollectionSerializable protocols you can encapsulate all of your object instantiation and collection logic in your data model layer.