1

I'm working with a non-profit organisation to help them develop a mobile application for their website (so they provided me with the backend services, etc)

Their login PHP service accepts data in JSON, and returns a true or false value in the form of JSON data, but I'm having trouble processing the response in Swift. I'm using Alamofire to connect to the HTTP service.

I'm attaching my code and the exception message I'm receiving below, would really appreciate some help

func authenticateUser (un: String, pw: String)  -> Bool
{
    var checker = false
    let jsonDict : [String: String] = ["volunteer_email": un, "volunteer_pass": pw]

     Alamofire.request("www.sampleurl.com/login.php", method: .post, parameters: jsonDict, encoding: JSONEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in

        switch(response.result) {
        case .success(_):
            if response.result.value != nil{
                print (response.result.value)
                let resp = response.result.value as! NSDictionary
                let results = resp["status"] as! [[String:Any]]
                //Change the boolean value of checker based on the value of the results constant
                print (results)
            }
            break

        case .failure(_):
            print(response.result.error)
            break
        }
    }

    return checker;
}

Log:

Optional(<__NSSingleObjectArrayI 0x600000009b50>( { status = false; } ) ) Could not cast value of type '__NSSingleObjectArrayI' (0x10e28c528) to 'NSDictionary' (0x10e28d1a8). 2018-05-09 12:13:00.177091+0530 TestApp1 [16680:817883] Could not cast value of type '__NSSingleObjectArrayI' (0x10e28c528) to 'NSDictionary' (0x10e28d1a8). (lldb)

Log for response.result.value:

Note: "status":"false"/"true" is the output from the web service

Optional(<__NSSingleObjectArrayI 0x60400001b160>( { status = false; } ))

Note: I did some research and I understood what the NSSingleObjectArray is and what causes it, but I'm stuck here as the service passes back only a single JSON value to my app. Is there any way to handle this without requesting the organisation to change their code? Logically, shouldn't I be able to cast the response into an NSDictionary regardless of its size?

Also, the reason why I've specified that the returned data can be of type any, is because I ran into an issue that can be found here:

Other StackOverflow question

Thanks so much in advance :)

Raghav
  • 470
  • 3
  • 13

2 Answers2

2

Since you're using .responseJSON of Alamofire, you should make use of Alamofire's parameterized enums. It provides the json object in .success. So doing case .success(_) is wasteful.

Go ahead with this and no need of typecasting response.result.value at all.

Alamofire
    .request("www.sampleurl.com/login.php",
             method: .post,
             parameters: jsonDict,
             encoding: JSONEncoding.default,
             headers: nil)
    .responseJSON { (response) in            
        switch(response.result) {
        case .success(let responseJSON):
            print(responseJSON)

            /*
             As an improvement:
             To obtain an easy-access object, convert responseJSON to either a:
              1. Codable model:
                 let model = JSONDecoder().decode(SomeModel.self, 
                                                  from: responseJSONData)
              2. SwiftyJSON object: (available on GitHub)
                 let json = JSON(responseJSON)

             Doing so will basically make accessing the inner elements easier.
             */...

        case .failure(let error):
            print(error)
        }
}

BTW, just FYI: the response is an array of dictionaries, not a dictionary of array of dictionaries.

staticVoidMan
  • 19,275
  • 6
  • 69
  • 98
  • When it comes to getting JSON data from a remote source you should never force-cast or make other assumptions. You never know when the server may return unexpected data. Always code defensively. Better to handle unexpected data gracefully than have the app crash on the user. – rmaddy May 09 '18 at 07:08
  • Didn't work, I got this: Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0). I understood your logic, thanks for that! But the problem's still persisting :/ – Raghav May 09 '18 at 07:08
  • Will definitely defensively code it before I release the product, just trying to get the call to work for now, to be honest – Raghav May 09 '18 at 07:10
  • @rmaddy Thanks. I know. For answers, depending on the OP's context I follow suit and put a disclaimer regarding force unwraps. Showing optional binding just increases the answer and explanation sometimes. – staticVoidMan May 09 '18 at 07:16
  • @Raghav Where does it crash? Is it the force unwrap? Optional bind it with `as?` and if it works then tell us what `print (response.result.value)` logs – staticVoidMan May 09 '18 at 07:17
  • @Raghav Updated answer. It's drastically different. It's incomplete for now. Tell me what `print(responseJSON)` logs and we'll take it ahead from there. – staticVoidMan May 09 '18 at 07:25
  • Added the response.result.value log value to my question – Raghav May 09 '18 at 07:26
  • This seems far better! The output is: ( { status = false; } ) – Raghav May 09 '18 at 07:34
  • @Raghav Great! Now you can use `SwiftyJSON` or better yet, a `Codable` model to proceed. __This answer is now complete.__ Proceed further with one of the options if required or be a cave man and use `responseJSON as [[String:Any]]`. Happy coding :) – staticVoidMan May 09 '18 at 08:29
  • @staticVoidMan Thank you so much! Much appreciated :) – Raghav May 09 '18 at 14:27
0

Use NSArray instead of NSDictionary

func authenticateUser (un: String, pw: String)  -> Bool{
var checker = false
let jsonDict : [String: String] = ["volunteer_email": un, "volunteer_pass": pw]

 Alamofire.request("www.sampleurl.com/login.php", method: .post, parameters: jsonDict, encoding: JSONEncoding.default, headers: nil).responseJSON { (response:DataResponse<Any>) in

    switch(response.result) {
    case .success(_):
        if response.result.value != nil{
            print (response.result.value)
            let resp = response.result.value as! NSArray
            let results = resp["status"] as! [[String:Any]]
            //Change the boolean value of checker based on the value of the results constant
            print (results)
        }
        break

    case .failure(_):
        print(response.result.error)
        break
      }
   }

return checker;
 }