I am working on my local json api and it works quite fine with receiving and parsing data using swiftyjson and alamofire, but when it comes to return these data, I have some troubles:
import Alamofire
import SwiftyJSON
...
func getApi() -> Array<JSON> {
let user = "user"
let password = "password"
Alamofire.request(.GET, "http://localhost/api/")
.authenticate(user: user, password: password)
.responseString { (req, res, body, error) in
if let data = (body)!.dataUsingEncoding(NSUTF8StringEncoding) {
let json = JSON(data: data)
println(json) // works fine
return json // does not work
}
}
}
So it prints "JSON is not convertible to Void"...
Anybody knows how to deal with this?
You think i am doing right using alamofire for a "http-basic-authentification" ?
Greetings and thanks!
If your api returns a JSON object, alamofire provides a .responseJSON, and swiftyJSON can wrap that response.
Also don't forget that this request is asynchronous, so you probably need to get these results in a completion handler, something like this:
func getApi(completionHandler: (jsonResponse: JSON) -> () {
let user = "user"
let password = "password"
Alamofire.request(.GET, "http://localhost/api/")
.authenticate(user: user, password: password)
.responseJSON { (req, res, JSON, error) in
println(json) // works fine
completionHandler(json)
}
}
}
Call the method like so:
getAPI(completionHandler: { (response: JSON) -> () in
// do something with your response. If the JSON contains an array you can iterate through it here.
}
Next good thing will be to check the "Response Serialization" section on Alamofire doc.
You are attempting to return an object of type JSON inside a block that has no return type (Void).
according to your variable names, you should set
res = json
or
res.setResponse(json)
or smt like that. Can't check now because I'm far away from my mac at the moment
Your function returns an array of JSON: [JSON].
You are trying to return a single JSON type object.
Change your return statement to return an array:
return [json] // should work
EDIT: I misread the code when I posted my answer. You are calling an async method, Alamofire.request. That method returns immediately, but the data isn't actually loaded until sometime later. Your current design won't work.
#Gwendle's answer is the right one. You need to refactor your method to take a completion block and put the response handling in that completion block.
Related
I have an issue understanding the process in consuming REST api services with SWIFT, seems like i'm missing something simple, but yet important here.
this is the singleton DataManager class, I'm using to consume API with loadNews() method, as you can see it's simple, request method, getter and initializer that will load the data.
for loadNews() I use Alamofire to handle request, and SwiftyJSON to parse the response.
class DataManager{
static let shared = DataManager()
private var data:JSON = JSON()
private init(){
print("testprint1 \(self.data.count)")
loadNews() { response in
self.data = response
print("initprint \(self.data.count)")
print(self.data["response"]["results"].count)
print(self.data["response"]["results"][0]["id"].stringValue)
}
print("testprint2 \(self.data.count)")
}
func getNews() -> JSON {
return data
}
func loadNews(completion: #escaping (JSON) -> ()){
Alamofire.request("...")
.responseJSON{ response in
guard response.result.isSuccess,
let value = response.result.value else {
print("Error: \(String(describing: response.result.error))")
completion([])
return
}
let json = JSON(value)
completion(json)
}
}
}
issue that i'm facing is when i try to call the DataManager() instance in my ViewController, I'm not able to read data in the controller for some reason, here is the controller code (relevant one):
class SecondViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
let data1 = DataManager.shared.getNews()
print("qa \(data1.count)")
}
...
}
now what bothers me is - logic behind this should be simple, let data1 = DataManager.shared.getNews() - if i'm not wrong will (should) execute the following flow:
init()->loadNews()->getNews()
initialize method will call loadNews, loadNews will fetch data from API, fill the data array, and getNews is supposed to return the filled data array, but that flow doesn't seem correct then
console output
console output text
testprint1 0
testprint2 0
qa 0
initprint 1
50
commentisfree/2019/dec/07/lost-my-faith-in-tech-evangelism-john-naughton
so it seems like both prints within init() get executed before loadNews() method that is between them, as well as "qa0" print that is printing the size of the array in the ViewController.
now my question is, does anyone see a mistake here, is this happening because of long network query, or am I just missing something, because it seems to me that data is properly loaded and parsed, which is seen in last 2 lines of output, but i can't get it where i need it, like it dissapears. is my logic here wrong? if someone could help I would really appreciate it.
The Alamofire process works asynchronously, but you don't consider it, that's the mistake.
Change the code to
class DataManager{
static let shared = DataManager()
func loadNews(completion: #escaping (JSON) -> ()){
Alamofire.request("...")
.responseJSON{ response in
guard response.result.isSuccess,
let value = response.result.value else {
print("Error:", response.result.error)
completion([])
return
}
let json = JSON(value)
completion(json)
}
}
}
class SecondViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
DataManager.shared.loadNews() { response in
print("initprint \(response.count)")
print(response["response"]["results"].count)
print(response["response"]["results"][0]["id"].stringValue)
}
}
...
}
You have to get the data from the completion handler in SecondViewController
I'm looking to run a Cloud-Code function from a Swift application and receive an object as a response. The response object from Parse is a standard JSON object as defined below and is not an object stored is Parse. Essentially, I'm looking to end up with an custom object defining the results of a cloud function's execution, not an object stored in the database.
I'm struggling with decoding the CloudCode response on the Swift side of things to a custom object following the Decodable protocol.
Sample Cloud Code
Parse.Cloud.define("MyCloudFunc", function(request, response) {
var results = {
"someBooleanProperty": true,
"someIntProperty": 1,
};
response.success(results);
}
Sample Swift Code
PFCloud.callFunction(inBackground: "MyCloudFunc", withParameters: []) { (result, error) in
// Printing `result` at this point shows what appears to be a JSON object.
guard let data = result as? Data else { return }
// Whatever type `result` actually is cannot be cast as Data, so we never make it past here.
guard let response = try? JSONDecoder().decode(MyDecodableStruct, from: data) else { return }
// DO SOMETHING WITH THE RESULT
}
Decodable Struct
struct MyDecodableStruct: Decodable {
var someBooleanProperty:Bool
var someIntProperty: Int
}
Question
How can I take that response from the Parse Cloud Code and end up with a decoded object of type MyDecodableStruct?
UPDATE
As suggested in the comments/answers, Parse is returning a Dictionary. I have been able to get everything working with the below; however, I feel there is a better way than double-conversion.
PFCloud.callFunction(inBackground: "MyCloudFunc", withParameters: []) { (result, error) in
guard let jsonString = result as? String else { return }
guard let data = jsonString.data(using: String.Encoding.utf8) else { return }
guard let response = try? JSONDecoder().decode(MyDecodableStruct.self, from: data) else { return }
// DO SOMETHING WITH RESULT.
}
Am I overlooking a way to convert from the Dictionary directly Data without doing the JSON conversion in-between?
Part of PFCloud's job is to create generic collection types from the cloud function response. Since the cloud function is answering a JS object, PFCloud should -- without the app noticing -- transmit JSON and parse it before invoking the callFunction callback.
So the posted cloud code result will the be a dictionary. Check to see that with...
if result is Dictionary<AnyHashable,Any> {
print("result is a Dictionary")
}
To convert that to the OP struct, add a from-dictionary initializer to it...
struct MyDecodableStruct: Decodable {
var someBooleanProperty:Bool
var someIntProperty: Int
init(dictionary: [AnyHashable,Any]) {
self.someBooleanProperty = dictionary["someBooleanProperty"] as? Bool ?? false
self.someIntProperty = dictionary["someIntProperty"] as? Int ?? 0
}
}
How to return what you got?Excuse me, novice, I understand only.enter image description here
enter image description here
Alamofire.request is an asynchronous task, so any code written after this is most likely going to be executed before the task is complete. There are a few ways of doing what you want, but since you haven't provided much information, I have just moved the JSON inside the closure. So you should get something to print out.
Alamofire.request("https://...").responseJSON { (response) in
// Inside this closure is where you receive your JSON
if let value = response.value {
let json = JSON(value)
let title = json["posts", 3, "title"].stringValue
print("Title:", title)
}
}
// Any code after this request will most likely be executed before
// the request has completed because it is done asynchronously.
This is another way that might work better for you.
I understand that you are a beginner and these kinds of operations can be quite complex. You need to understand the order at which code get executed, and how variables work. You are getting that error because swiftyJsonVar is declared in a block that is not accessible to code in the viewDidLoad. I suggest you learn about multi-threading and other asynchronous tasks and probably learn how to declare and use variables properly.
override func viewDidLoad() {
super.viewDidLoad()
load { (json) in
if let json = json {
let title = json["posts", 3, "title"].stringValue
print("Title:", title)
}
}
}
func load(completion: #escaping (JSON?) -> Void){
Alamofire.request("https://httpbin.org/get").responseJSON { (response) in //https://...
var json: JSON?
if let value = response.value {
json = JSON(value)
}
completion(json)
}
}
here is my issue, I would like to create a function with this prototype :
func doPostRequest(......)->JSON()
And I write it like that :
func downloadData(completed:#escaping()->()){
Alamofire.request(url).responseJSON(completionHandler: {
response in
let result = response.result
if let dict = ... {
self._temp = String(format: "%.0f °C", temp - 273.15)
...
}
completed()
})
}
I'd like to return an Any object or dictionary, something with my JSON in... but each time I try to implement return I have a nil object ! Maybe a scope problem how can I implement this function to have
var myJson:NSDictionary
myJson=downloadData(......) ???
Thanks for your help
Since the method in the body works asynchronously you have to declare your request method with an completion handler for example
func doPostRequest(completion: #escaping ([String:Any])->())
On return it passes a Swift dictionary.
The method can be used with this code:
var myJson = [String:Any]()
...
doPostRequest() { json in
self.myJson = json
// do something with the returned data
}
first you need to create a ObjectMapper to map your objects and use AlamofireObjectMapper to get
try this code
request(url, method: .post, parameters:params).validate().responseObject{(response:
DataResponse<objectMapperclass>)in
switch response.result{
case.success(let data):
let objects = data
case.faliure(_):
}
}
[
{
"ID": 0,
"Name": "PHI"
},
{
"ID": 0,
"Name": "ATL"
}
]
I'm using SwiftyJSON and Alamofire. This is what is being returned. I want to loop through each of these objects now in my code. I'm having trouble getting this information though.
json[0]["Name"].string seems to return nil, and I'm not sure why. The JSON object is definitely getting the JSON, when I print it to the console it looks exactly like above.
I also tried:
var name = json[0].dictionary?["Name"]
Still nil though.
Any ideas?
EDIT
Here's the code I'm using to retrieve the JSON.
Alamofire.request(.GET, "url", parameters: nil, encoding: .JSON, headers: nil).responseJSON
{
(request, response, data, error) in
var json = JSON(data!)
//var name = json[0].dictionary?["Name"]
}
Your JSON is valid (at least this snippet is), and your first method to retrieve the data from SwiftyJSON is correct.
On the other hand, the Alamofire snippet you showed didn't compile for me, I had to change the signature.
Given that in the comments you say that not only json[0]["Name"] is nil but json[0] is also nil, I think you have a problem with how your data is fetched.
I tested this version of the Alamofire method in a sample app and it worked well:
Alamofire.request(.GET, yourURL).responseJSON(options: nil) { (request, response, data, error) in
let json = JSON(data!)
let name = json[0]["Name"].string
println(name)
}
In the screenshot, the URL is my local server, with your JSON copy pasted in "test.json".
This "answer" is an extension of my comment, I needed room to show more info...
I think you might try this.
first convert JSON(data) using swifyJSON
let json = JSON(data!)
then count the array element in data.
let count: Int? = json.array?.count
after that use for loop to reach array element one.
for index in 0...count-1 {
self.idArray.append(json[index]["ID"].stringValue)
self.nameArray.append(json[index]["Name"].stringValue)
}
you will get sorted data in "idArray" and "nameArray". try to print both the array.
You do not have to use index to parse array. You can get single json object from array and use it to access the data:
for (index: String, subJson: JSON) in json {
println("Current Index = \(index)")
if let _id = subJson["ID"].int {
println("ID = \(_id)")
} else {
println("Error ID")
}
if let _name = subJson["Name"].string {
println("Name = \(_name)")
} else {
println("Error Name")
}
}