HTTP Request and JSON parsing in Swift - json

So, I'm trying to make a very simple watchOS app in XCode. It consists of a button, two labels and a separator between the two labels. It is a digital assistant app, and needs to interface with Dialogflow (https://dialogflow.com).
The button calls the presentTextInputController function, and I want to use that result as a query to my Dialogflow agent.
I need to make an HTTP request, which in JS would look more like this:
{
url:"https://api.api.ai/v1/query",
method:"post",
body:JSON.stringify({query:"userInput",lang:"en-US",sessionID:"yaydevdiner"}),
headers:{
contentType:"application/json; charset=utf-8",
Authorization:"Bearer <auth_token>"
}
}
The response is a JSON object, and I need to access the jsonObject["result"]["speech"] value as a String to use Label.setText()
Everything I've tried has given errors about type Any and other such things. I also haven't been able to do much debugging since the print output isn't showing up in XCode.
I must mention that I'm an extreme beginner to Swift, and I am not good at handling their types and casting and unpacking and things like that.
Could someone show me how I might handle this request and the subsequent processing of the JSON?
Here is my current code:
//HTTP Request
let parameters = [
"query":name![0] as? String,
"lang":"en-US",
"sessionID":"yaydevdiner"
];
//create the url with URL
let url = URL(string: "https://api.api.ai/v1/query")! //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: parameters, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer f786fef55008491fb8422cea2be85eb1", forHTTPHeaderField: "Authorization")
//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] {
self.Response.setText(json["result"]["string"]);
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
Response is a text label.
This code gives me an error saying I should have a question mark between
json["result"] and ["speech"]. When I do this, it gives me another error saying "Type Any has no subscript members".

Ok, I figured it out.
Because XCode automatically makes an iOS app with the watchOS app, I decided to try debugging in the iOS app until I got the HTTP request and JSON parsing right.
Inside the JSONSerialization if statement, I had to add another if statement:
if let result = responseJSON["result"] as? [String:Any]{
self.Response.setText(result!["speech"] as? String ?? "Network error Occurred")
}
Thanks for the help from vadian!

Related

Invalid top-level type in JSON write Swift 4

I am trying to learn JSON parsing. I have written an API in Laravel, which returns status : 200 in response. What I did is this:
guard let url = URL(string: "http://localhost/workon-api/public/api/register") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let newUser = User.init(name: "Rob", email: "abc#gmail.com", password: "12345678")
do {
let jsonBody = try JSONEncoder().encode(newUser)
request.httpBody = jsonBody
} catch { }
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
print(json)
} catch {}
}.resume()
Now, I am getting this error: Invalid top-level type in JSON write and app's crashing. After searching, I used this:
let json = try JSONSerialization.jsonObject(with: data, options: [])
And, it works. Why the previous method is not working? And, I get a response like this if I try to return the collected userInfo.
status = "{\"name\":\"Rob\",\"email\":\"abc#gmail.com\",\"password\":\"12345678\"}";
Why are back-slashes there? Are these okay? And, what is Gzip data? I know I am asking a lot, but I need to understand this. Thanks in advance.
P.S. : Here is the User Model.
struct User: Encodable {
let name : String?
let email : String?
let password : String?
}
First of all the backslashes are virtual. The framework adds them to be able to print double quotes within a literal string.
Secondly dataTask returns serialized JSON Data so to get a dictionary or array from the data you have to call jsonObject(with.
let object = try JSONSerialization.jsonObject(with: data)
print(object)

Parsing nested Json data in swift 3

I am getting unexpected values back when i am parsing my json data from my api, i may be doing something wrong here as i'm quite new to swift but i was getting correct values before when i was receiving one "key" but now i have added two i cannot seem to parse the values properly.
This is the json collected from the address my code is receiving, (sorry if its hard to read havn't worked out how to do line breaks yet in my ruby api)(as long as its functional im not too worried at the moment)
{
"ratings":{
"elements":{"Ready Position":[{"description":"Neutral Grip","values":"1,2,3,4,5"},{"description":"Back Straight (Concave ir Convex?)","values":"1,2,3,4,5"},{"description":"Body Low \u0026 Feet a little more than sholder width apart","values":"1,2,3,4,5"},{"description":"Weight on Balls of Feet","values":"1,2,3,4,5"},{"description":"Head Up","values":"1,2,3,4,5"},{"description":"Sholder Blades Close","values":"1,2,3,4,5"},{"description":"Eyes Drilled","values":"1,2,3,4,5"}],"Split Step":[{"description":"Ready Position Conforms","values":"Yes,No"},{"description":"Body Position Low","values":"1,2,3,4,5"},{"description":"Legs Loaded/Prepared","values":"1,2,3,4,5"}]}
},
"comments":{}
}
Now, My swift code looks like this
let playerAPIurl = "http://linkcoachuat.herokuapp.com/api/v1/session/element?organisation=" + userorganisation + "&group=" + urlGroupSelected + "&sport=" + usersport
print(playerAPIurl)
var request = URLRequest(url: URL(string: playerAPIurl)!)
request.httpMethod = "GET"
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: OperationQueue.main)
let task = session.dataTask(with: request) { (data, response, error) in
if error != nil {
print("ERROR")
}
else{
do {
let json = try JSONSerialization.jsonObject(with: data!) as? [String: AnyObject]
print(json)
And this is the output im getting from this print(json)
Optional({
comments = {
};
ratings = {
};
})
I know i shouldnt be getting anything more in the comments part, but in the ratings part there should be some data?
so after recieving the json and dealing with parsing it i need to access this part of it ["ratings"]["elements"] and after that im all good
thanks in advance and please bare in mine im very new to swift
Thanks
Try the below code. The url used in below code has your JSON data. This code is printing the output correctly.
func testApi(){
let url = URL(string: "https://api.myjson.com/bins/jfccx")
let session = URLSession.shared
let request = URLRequest(url: url!)
//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 let data = data, error == nil else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
print(json)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}

JSONSerialization error in swift3

I am trying to post Audio file to an API, but I am getting an error
"*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (NSURL)'"
Here is my code:
let parameters = [fileUrl]
//create the url with NSURL
let url = NSURL(string: "httpblahblah")
//create the session object
let session = URLSession.shared
//now create the NSMutableRequest object using the url object
let request = NSMutableURLRequest(url: url! as URL)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
//HTTP Headers
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: AnyObject] {
print(json)
// handle json...
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
When you create request.httpBody, you're passing in the parameters array you declared above:
let parameters = [fileUrl]
It's not clear from your code, but if fileUrl is an NSURL instead of a String, that would cause the error you see. The documentation for JSONSerialization says:
All objects are instances of NSString, NSNumber, NSArray,
NSDictionary, or NSNull.
so trying to sneak in an NSURL will definitely not work.
Assuming that your intention is to serialize fileUrl as a String, you can do that by changing the declaration to:
let parameters = [fileUrl.absoluteString!]
If you want to send the actual file data, you're not going to be able to serialize it in a JSON object. Check out this question and its answer for a good example of sending data from a file to a server. Depending on the API you're working with you may still need the JSON object for other data, and its structure will be determined by the API.
From what I understand you are trying to upload a audio file, with a URL reference instead of Data.
Get correct Data format from URL with this:
let data = FileManager.default.contents(atPath: fileUrl.path)
request.httpBody = data

Trouble converting JSON to data with Swift

I am trying to learn Swift. One of my projects is to try to retrieve JSON data from an internal web service (a group of Python CGI scripts) and convert it into a Swift object. I can do this easily in Python, but I am having trouble doing this in Swift. Here is my playground code:
import UIKit
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
let endpoint: String = "http://pathToCgiScript/cgiScript.py"
let url = NSURL(string: endpoint)
let urlrequest = NSMutableURLRequest(URL: url!)
let headers: NSDictionary = ["User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)",
"Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"]
urlrequest.allHTTPHeaderFields = headers as? [String : String]
urlrequest.HTTPMethod = "POST"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlrequest) {
(data, response, error) in
guard data != nil else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("Error calling script!")
print(error)
return
}
do {
guard let received = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as?
[String: AnyObject] else {
print("Could not get JSON from stream")
return
}
print(received)
} catch {
print("error parsing response from POST")
}
}
task.resume()
I know making a 'POST' to retrieve data may look odd, but that is how the system is set up. I keep on getting:
Could not get data from JSON
I checked the response, and the status is 200. I then checked the data's description with:
print(data?.description)
I got an unexpected result. Here is a snippet:
Optional("<0d0a5b7b 22535441 54555322 3a202244 6f6e6522 2c202242 55535922...
I used Mirror, and apparently the type is NSData. Not sure what to make of this. I have tried to encode the data with base64EncodedDataWithOptions. I have tried different NSJSONReadingOptions as well to no avail. Any ideas?
Update:
I used Wireshark to double check the code in the Playground. Not only was the call made correctly, but the data being sent back is correct as well. In fact, Wireshark sees the data as JSON. The issue is trying to turn the JSON data into a Swift object.
I figured out what was wrong. I was casting to the wrong type. This is the new code:
guard let received = try! NSJSONSerialization.JSONObjectWithData(data!, options:.AllowFragments) as? [AnyObject]
The JSON was not returning an array of dictionaries but an array of objects.

Send POST request with JSON object and query param to REST webservice using alamofire

I'm trying to send POST request to REST webservice using alamofire
I'm passing json object as POST body, and i'm getting the response and everything works fine till now
Alamofire.request(.POST, path, parameters: createQueryParams(), encoding: .JSON)
.responseArray { (request, response, myWrapper, error) in
if let anError = error
{
completionHandler(nil, error)
println("Error in handling request or response!")
return
}
completionHandler(myWrapper, nil)
}
private class func createQueryParams() -> [String:AnyObject]{
var parameters:[String:AnyObject] = [String:AnyObject]()
parameters["lat"] = lLat!
parameters["lng"] = lLon!
if category != nil { // here is the problem
parameters["category"] = category!
}
return parameters
}
I have a category filter, if there is a value in category variable, i want to send it as QueryParam (should encoding be .URL? but how can i send json object ??)
this code does not work
if category != nil {
parameters["category"] = category!
}
How can i do this? Hope I can explain it clearly
Thanks in advance
You could solve it this way:
let mutableUrlRequest = NSMutableUrlRequest(URL: URL.URLByAppendingPathComponent(path)
mutableUrlRequest.HTTPMethod = .POST
let request = Alamofire.ParameterEncoding.URL.encode(mutableUrlRequest, parameters: createQueryParameters()).0
Alamofire.request(request)
However, I would advise you to look into the Router declaration of Alamofire and try this one. With it you can create dynamic requests and all of them are declared in a single file.
Edit:
Oh wait you can forget the previous edit the solution is quite simple and you also answered it by yourself. Yes you just have to change the encoding to .URL, you still are able to send json objects, because Alamofire itself decodes then the json object to a string for queryparams.
Alamofire.request(.POST, path, parameters:createQueryParams(), encoding: .URL).responseArray...
Edit 2:
Since the first edit did not work, try this:
let url = NSURL(string: path)!
let urlRequest = NSURLReqeust(URL: url)
request.HTTPMethod = "Post"
let encoding = Alamofire.ParameterEncoding.URL
let request = encoding.encode(urlRequest, createQueryParams())
Alamofire.request(request)