Search code examples
pythonjsonswiftalamofirealamofire5

jsonbin API problems with Swift


I'm building a little app that talks to the jsonbin API with Alamofire. Their API docs cover using Python to "put" or "update" the JSON file data extensively in Python. I'm working on doing that in Swift 5.

My problem is when I attempt to use a put command to send data to a specific file. See code:

import AlamoFire

func sendData()
    {
        let headers: HTTPHeaders = [
            "Content-Type": "application/json",
            "X-Master-Key": "x-x-x-x-x-x-x-x",
            "X-Bin-Meta": "false"
        ]
        
        let queue = DispatchQueue(label: "com.app.name", qos: .background, attributes: .concurrent)
        
        do{
            
            var testData = [
            
                "title": "My Test Task",
                "date": "My Date",
                "complete": "Completion Status",
                "assignee": "Bob Jones"
            
            ]
            
            AF.request("https://api.jsonbin.io/v3/b/x-x-x-x-x-x-x", method: .put, parameters: testData, headers: headers).validate().responseJSON(queue: queue) { response in
                switch response.result {
                case .success(_):
                    print("Success!")
                case .failure(let error):
                    print(error)
                }
            }
        }
        catch
        {
            print("Error occured.")
        }

        
    }

I'm trying to send a simple JSON structure over. The error I receive is this:

esponseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: 400))

Their API docs say that an error 400 typically means:

  • You need to pass Content-Type set to application/json

  • Invalid Bin Id provided

  • Bin (JSON Data) cannot be blank

I have verified all of these requirements are correct.

Here is functioning Python code as per their docs:

import requests
url = 'https://api.jsonbin.io/v3/b/x-x-x-x-x-x'
headers = {
  'Content-Type': 'application/json',
  'X-Master-Key': 'x-x-x-x-x-x-x'
}
data = {"sample": "Hello World"}

req = requests.put(url, json=data, headers=headers)
print(req.text)

Is there something I'm missing? Are the data types different? Is the testData Swift variable different than the data Python variable? How might I go about "putting" data to the RESTful API with Swift and Alamofire 5?

Thanks!


Solution

  • Your current parameters are not sent as JSON, but url encoded.

    You can use the cURL description tool method of Alamofire to see how it's sent:

    AF.request("https://api.jsonbin.io/v3/b/x-x-x-x-x-x-x",
               method: .put,
               parameters: testData,
               headers: headers)
    .cURLDescription { print("JSONBIN curl: \($0)") }
    .validate()
    .responseJSON(queue: queue) { response in
        switch response.result {
        case .success(_):
            print("Success!")
        case .failure(let error):
            print(error)
        }
    }
    

    And you'd get:

    JSONBIN curl: $ curl -v \
        -X PUT \
        -H "Accept-Language: fr-US;q=1.0" \
        -H "User-Agent: Alamofired/1.0 (ZzZ; build:1; iOS 16.4.0) Alamofire/5.4.0" \
        -H "Accept-Encoding: br;q=1.0, gzip;q=0.9, deflate;q=0.8" \
        -H "Content-Type: application/json" \
        -H "X-Bin-Meta: false" \
        -H "X-Master-Key: x-x-x-x-x-x-x-x" \
        -d "assignee=Bob%20Jones&complete=Completion%20Status&date=My%20Date&title=My%20Test%20Task" \
        "https://api.jsonbin.io/v3/b/x-x-x-x-x-x-x"
    

    The important part being

    -d "assignee=Bob%20Jones&complete=Completion%20Status&date=My%20Date&title=My%20Test%20Task"
    

    So you need to tell to send the parameters as JSON:

    AF.request("https://api.jsonbin.io/v3/b/x-x-x-x-x-x-x",
               method: .put,
               parameters: testData,
               encoder: JSONParameterEncoder.default,
               headers: headers)
    .cURLDescription { print("JSONBIN curl: \($0)") }
    .validate()
    .responseJSON(queue: queue) { response in
        switch response.result {
        case .success(_):
            print("Success!")
        case .failure(let error):
            print(error)
        }
    }
    

    And then, the cURL output parameter line is:

    -d "{\"date\":\"My Date\",\"title\":\"My Test Task\",\"complete\":\"Completion Status\",\"assignee\":\"Bob Jones\"}" \
    

    Unrelated to your code, but: There is no need for a do/catch, there is no try, and it would be recommended to print the error if there is really an try, not just print("Error occured."). but print("Error occurred: \(error)").

    Also, responseJSON() is deprecated, in favor of Codable and responseDecodable(of:). If you still want to use JSONSerialization (which is innerly called in responseJSON()), use responseData() and call it yourself.