Sunday, January 15, 2017

Using RestKit to make a JSON post with Swift



While working on an iPad app using Swift 2, I came across the need to POST some JSON data in the body of a request to our server. Here's a simple example of how to accomplish that.

Note: This post assumes you already use RestKit in your project. Adding it to your build is covered by other articles on the web.

Before we jump into the code to do this, here's the JSON that we want to send:
{
  "dog_breed": "whippet",
  "pounds": 35,
  "height": 20,
  "age": 1
}

First, we'll make a class to hold the data we're POSTing:
import Foundation

class DogRequest: NSObject {
  var dog_breed: String? = nil
  var pounds: Int = 0
  var height: Int = 0
  var age: Int = 0
}

Now we make a mapping for the request data:
let dogRequestMapping: RKObjectMapping = RKObjectMapping(forClass:NSMutableDictionary.self)
dogRequestMapping.addAttributeMappingsFromArray(["dog_breed", "pounds", "height", "age"])
Note that the mapping will end up as a dictionary, so that's what we use for the forClass argument.

Now we create a descriptor for the request:
let dogDescriptor: RKRequestDescriptor = RKRequestDescriptor(mapping: dogRequestMapping,
                                                             objectClass: DogRequest.self,
                                                             rootKeyPath: nil,
                                                             method: RKRequestMethod.POST)
objectManager.addRequestDescriptor(dogDescriptor)

If your server expects a different base or root for your post data, you can specify that with a string passed to rootKeyPath.
For example, if we had rootKeyPath: "woof", the resulting JSON would look something like this:
{
  "woof": {
    "dog_breed": "whippet",
    "pounds": 35,
    "height": 20,
    "age": 1
  }
}

OK, so now we have the request side taken care of.  We take similar steps to get the result of our POST setup.  First, here's the result class:

import Foundation

class DogResponse: NSObject {
  var success: Bool = false
  var error: String? = nil
  var dog_breed: String? = nil
  var count: Int = 0
}


And here's a mapping for the response data:
let dogResponseMapping: RKObjectMapping = RKObjectMapping(forClass:DogResponse.self)
dogResponseMapping.addAttributeMappingsFromArray(["success", "dog_breed", "count", "error"])

And then the descriptor for the response:
let successCodes = RKStatusCodeIndexSetForClass(UInt(RKStatusCodeClassSuccessful))
let dogRespDesc: RKResponseDescriptor = RKResponseDescriptor(mapping: dogResponseMapping,
                                                             method: RKRequestMethod.POST, 
                                                             pathPattern: "dog/breed", 
                                                             keyPath: nil
                                                             statusCodes: successCodes)
objectManager.addResponseDescriptor(dogRespDesc)

Now, we'll use all this to actually send the request.

Let's create the data we want to POST:
let dog : DogRequest = DogRequest()
dog.dog_breed = "whippet"
dog.pounds = 35
dog.height = 20
dog.age = 1

Now, since we want to send the data as JSON (application/json) instead of the default (application/x-www-form-urlencoded), it's very important to tell RK what MIME type we want the data serialized into:
RKObjectManager.sharedManager().requestSerializationMIMEType = RKMIMETypeJSON

And finally, we make the actual POST and handle the response we get:
RKObjectManager.sharedManager().postObject(dog, path: "dog/breed", parameters: nil,
  success: {(operation: RKObjectRequestOperation!, mappingResult: RKMappingResult!) -> Void in
    let dogResponse : DogResponse = mappingResult.array().first as! DogResponse
    if dogResponse.success {
      // Yay!  We successfully posted the dog data
      print("There are now \(dogResponse.count) \(dogResponse.dog_breed) in the server!")
    } else {
      // Uh oh! We received an error response from the server
      print("Error in response: \(dogResponse.error)")
    }
  }, failure: {(operation: RKObjectRequestOperation!, error: NSError?) -> Void in
    // Oh no.  There was an error before it reached the server.
    print("ERROR': \(error)")
})

Pulling it all together, we get this:

File: DogRequest.swift
import Foundation

class DogRequest: NSObject {
  var dog_breed: String? = nil
  var pounds: Int = 0
  var height: Int = 0
  var age: Int = 0
}


File: DogResponse.swift
import Foundation

class DogResponse: NSObject {
  var success: Bool = false
  var error: String? = nil
  var dog_breed: String? = nil
  var count: Int = 0
}



File: ServerApi.swift
let baseURL: NSURL = NSURL(string: "https://dogs.example.com/public/v1/")!
let client: AFHTTPClient = AFHTTPClient(baseURL: baseURL)
let objectManager: RKObjectManager = RKObjectManager(HTTPClient: client)

//...

let dogRequestMapping: RKObjectMapping = RKObjectMapping(forClass:NSMutableDictionary.self)
dogRequestMapping.addAttributeMappingsFromArray(["dog_breed", "pounds", "height", "age"])

let dogDescriptor: RKRequestDescriptor = RKRequestDescriptor(mapping: dogRequestMapping,
                                                             objectClass: DogRequest.self,
                                                             rootKeyPath: nil,
                                                             method: RKRequestMethod.POST)
objectManager.addRequestDescriptor(dogDescriptor)

let dogResponseMapping: RKObjectMapping = RKObjectMapping(forClass:DogResponse.self)
dogResponseMapping.addAttributeMappingsFromArray(["success", "dog_breed", "count", "error"])

let successCodes = RKStatusCodeIndexSetForClass(UInt(RKStatusCodeClassSuccessful))
let dogRespDesc: RKResponseDescriptor = RKResponseDescriptor(mapping: dogResponseMapping,
                                                             method: RKRequestMethod.POST, 
                                                             pathPattern: "dog/breed", 
                                                             keyPath: nil
                                                             statusCodes: successCodes)
objectManager.addResponseDescriptor(dogRespDesc)

// ...

let dog : DogRequest = DogRequest()
dog.dog_breed = "whippet"
dog.pounds = 35
dog.height = 20
dog.age = 1

RKObjectManager.sharedManager().postObject(dog, path: "dog/breed", parameters: nil,
  success: {(operation: RKObjectRequestOperation!, mappingResult: RKMappingResult!) -> Void in
    let dogResponse : DogResponse = mappingResult.array().first as! DogResponse
    if dogResponse.success {
      // Yay!  We successfully posted the dog data
      print("There are now \(dogResponse.count) \(dogResponse.dog_breed) in the server!")
    } else {
      // Uh oh! We received an error response from the server
      print("Error in response: \(dogResponse.error)")
    }
  }, failure: {(operation: RKObjectRequestOperation!, error: NSError?) -> Void in
    // Oh no.  There was an error before it reached the server.
    print("ERROR': \(error)")
})