swift json how to fill an object - 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(_):
}
}

Related

Can't load data into ViewController from a JSON response swift

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

Return JSON data from Parse Cloud Code to a Decodable Struct in Swift

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 iterate through JSON

I have a method which performs a GET request to an API:
public func getApiData(completion: #escaping () -> (), fullUrl: String)
{
let session = URLSession.shared
let url = URL(string: fullUrl)!
let task = session.dataTask(with: url) { (data, _, _) -> Void in
if let data = data {
self.serializeToJSON(jsonData: data)
completion()
}
}
task.resume()
}
Using SwiftyJSON I then convert the data into JSON:
private func serializeToJSON(jsonData: Data) {
self.json = JSON(data: jsonData)
print(self.json)
for (index,item) in self.json {
print("hi")
}
}
Printing the full JSON gives:
[{"TenantID":1,"Tenant1":"RAC"},{"TenantID":2,"Tenant1":"VictorMillwell"},{"TenantID":3,"Tenant1":"Comfort"},{"TenantID":4,"Tenant1":"Greenlight"}]
However the JSON can't be iterated through as the print("hi") isn't executed, I'm not sure why, I've looked everywhere on the internet to understand why it doesn't iterate and I cant seem to understand why.
Does anyone know why?
There is a good tutorial here, but in the manual it says you can loop like this:
// If json is .Dictionary
for (key,subJson):(String, JSON) in self.json {
// Do something you want
}
// If json is .Array
// The `index` is 0..<json.count's string value
for (index,subJson):(String, JSON) in self.json {
// Do something you want
}
if you don't know if it's a dictionary or array, maybe you can do it like this:
switch self.json.type {
case .array:
for (index,subJson):(String, JSON) in self.json {
// Do something you want
}
case .dictionary:
for (key,subJson):(String, JSON) in self.json {
// Do something you want
}
default:
// Do some error handling
}
It isn't clear why you want to enumerate the JSON. It's trivial to decode this JSON in Swift 4:
struct Tenant:Decodable { let TenantID:Int; let Tenant1:String }
let arr = try! JSONDecoder().decode([Tenant].self, from: data)
Now arr is a Swift array of Tenant, where each Tenant has a TenantID property and a Tenant1 property. And now you can do whatever you like with that array, including cycling through it if you wish.

Copy local dictionary into global dictionary

I am having a problem where I create a dictionary in a method and then try to make a field equal to the dictionary!
So:
var theJson : NSDictionary!
func someMethod()
{
if let theDictionary = valueFromGoogleDirections as? NSDictionary
theJson = theDictionary
}
func fieldTest()
{
print(theJson)
}
after calling someMethod and then fieldTest the fieldTest method always prints "{ }" which I figure means it is an empty dictionary. What is going on? I know that theDictionary is getting values because I am able to use its values from within the someMethod method.
try this:
theJson = theDictionary.copy()

SwiftyJSON - Returning json array from func

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.