0

I am trying to collect data from user and send it to an web service, however I get an error "invalid top-level type in JSON write" I am collecting the steps from the healthstore in another function and all the data is passed into the userhealthprofile variables correctly as it works printing them out. However something is wrong with my JSON code

Full error message is

2017-10-17 09:30:57.170950+0200 IphoneReader[347:40755] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** +[NSJSONSerialization dataWithJSONObject:options:error:]: Invalid top-level type in JSON write'
*** First throw call stack:
(0x1d0b3b3d 0x1c33b067 0x1d0b3a85 0x1da763c1 0x9aa50 0x9bb60 0x2231a3b5 0x2231a349 0x22304979 0x22321f87 0x2286bf1b 0x22868833 0x22868423 0x22867849 0x223146f5 0x222e62bb 0x22a797f7 0x22a7419b 0x22a7457d 0x1d06ffdd 0x1d06fb05 0x1d06df51 0x1cfc11af 0x1cfc0fd1 0x1e76bb41 0x22349a53 0xa4d18 0x1c7ae4eb)
libc++abi.dylib: terminating with uncaught exception of type NSException

Code

@IBAction func submitAction(sender: AnyObject) {

    userHealthProfile.name = nameLabel.text
    print(userHealthProfile.name)

    userHealthProfile.age = Int(ageLabel.text!)
    print(userHealthProfile.age)

    userHealthProfile.heightInMeters = Int(heightLabel.text!)
    print(userHealthProfile.heightInMeters)

    userHealthProfile.weightInKilograms = Int(weightLabel.text!)
    print(userHealthProfile.weightInKilograms)

    for element in stepy.step {
        print(element)
    }

    userHealthProfile.totalStepsCount = stepy.step

    print("pressing button")

    //create the url with URL
    let url = URL(string: "www.thisismylink.com/postName.php")! 
    //change the url

    //create the session object
    let session = URLSession.shared

    //now create the URLRequest object using the url object
    var request = URLRequest(url: url)
    request.httpMethod = "POST" //set http method as POST

    do {
        request.httpBody = try JSONSerialization.data(withJSONObject: userHealthProfile, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
    } catch let error {
        print(error.localizedDescription)
    }

    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")

    //create dataTask using the session object to send data to the server
    let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in

        guard error == nil else {
            return
        }
        guard let data = data else {
            return
        }
        do {
            //create json object from data
            if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
                print(json)
                // handle json...
            }
        } catch let error {
            print(error.localizedDescription)
        }
    })
    task.resume()
}

This is what userprofile looks like, this is what I need to send as a jsonobject for my web service.

class UserHealthProfile {

    var name: String?
    var age: Int?
    var totalStepsCount: Array<Any>!
    var heightInMeters: Int?
    var weightInKilograms: Int?

}
  • Add the error you are getting... – Ladislav Oct 17 '17 at 08:58
  • Added full error –  Oct 17 '17 at 09:08
  • Looks like userHealthProfile is not a `[[String:Any]]` or `[String:Any]` – Marcel Oct 17 '17 at 10:34
  • Show us the json that is being returned...you can add this before the `do try catch` `print(String(data: data, encoding: .utf8)` and copy and paste JSON you get from the server here and then we will be able to help... – Ladislav Oct 17 '17 at 10:43
  • Added the format I am trying to send, but no idea what you want me to do -Ladislav –  Oct 17 '17 at 10:47
  • rewrite catch like `catch let error {print(String(data: data, encoding: .utf8) print(error.localizedDescription) }` then copy paste everything you see in console – Ladislav Oct 17 '17 at 11:18
  • `JSONSerialization.data(withJSONObject: userHealthProfile, options: .prettyPrinted)` this is causing a crash, right? Why? Because `userHealthProfile` is a custom object, not a `(NS)Dictionary` nor a `(NS)Array`, read the doc https://developer.apple.com/documentation/foundation/jsonserialization you need to have a method `toDict()` to transform a `UserHealthProfile` object into a Dictionary. – Larme Oct 17 '17 at 11:31
  • Aha, how do I use todict? –  Oct 17 '17 at 11:33
  • 1
    You create it yourself according to the documentation of your web API. – Larme Oct 17 '17 at 11:37
  • Well, the web AP expects basically the userhealthprofile class I provided as a jsonobject. –  Oct 17 '17 at 11:46
  • https://stackoverflow.com/questions/43126393/turn-swift-object-into-a-json-string ? You know how to create a dict, right? – Larme Oct 17 '17 at 13:00
  • No idea Larme, but your link seems to be perfect for that. Thanks a lot, will read up on it and get started! :D –  Oct 17 '17 at 13:03

1 Answers1

0

I think you should use Alamofire for calling web API.. Here is the sample code according to your requirements.

I hope this will help you...

func Submit(parametersToSend:NSDictionary)
{


Alamofire.request(.POST, "http://www.thisismylink.com/postName.php",parameters: parametersToSend as? [String : AnyObject], encoding: .URL).responseJSON
    {
        response in switch response.2
        {
        case .Success(let JSON):
            print(JSON)
            let response = JSON as! NSDictionary
            print("My Web Api Response: \(response)")
            break
        case .Failure(let error):
            print("Request failed with error: \(error)")
            break
        }

     }

}

Function Calling in your @IBAction func submitAction(sender: AnyObject) like this

@IBAction func submitAction(sender: AnyObject) {

let steps  = stepy.step
let name = nameLabel.text!
let age = Int(ageLabel.text!)
let height = Int(heightLabel.text!)
let weight = Int(weightLabel.text!)
let parameters = [
      "name": name
      "age": age
      "totalStepsCount": steps
      "heightInMeters": height
      "weightInKilograms": weight
       ]
 self.submit(parametersToSend:parameters)

}
Zee
  • 327
  • 1
  • 8
  • This is really awesome and really simplified the code I needed. I'm not originally a swift programmer but had to switch to swift because the health kit code wouldn't work in C# xamarin, so thanks, will try this out. –  Oct 18 '17 at 08:18
  • I get an error on this part of the code Alamofire.request(.POST, "http://www.thisismylink.com/postName.php",parameters: parametersToSend as? [String : AnyObject], encoding: .URL).responseJSON Says "http://www.thisismylink.com/postName.php" is an extra argument in call. –  Oct 18 '17 at 11:02
  • your are using swift 3 or swift 4? – Zee Oct 18 '17 at 11:33
  • Apparently Swift 4 –  Oct 18 '17 at 11:55
  • Alamofire Version? – Zee Oct 18 '17 at 11:58
  • 4.5.1 version am I using –  Oct 18 '17 at 12:23
  • Sorry i have not use Swift 4 and Alamofire 4.5.1 yet so please read the documentation of Alamofire https://github.com/Alamofire/Alamofire/blob/master/README.md#response-json-handler> – Zee Oct 18 '17 at 12:29
  • I see, seem to get the same problem with the updated guideline, wasn't much that needed to be changed but seems to be it is a problem with my parameters somewhere, also is it possible to downgrade my alamofire to your version? –  Oct 20 '17 at 07:34