Swift make Generic function from JSON request - json

I'm using JSONRPCKit lib, so there's final request contains that Request
let request1 = RPCRequest(params: SomeParams)
let batch1 = batchFactory.create(request1)
let httpRequest1 = MyServiceRequest(batch: batch1)
Session.send(httpRequest1){ result in
switch result {
case .success(let auth):
let gson = JSON(auth)
print(gson)
case .failure(let error):
print("Error: ", error)
}
}
I have to make a lot of requests like this. So i want to make it Generic to keep reuse it not typing everything again.
Could you please help me?

Just create a generic method which wraps these code inside something like this,
func sendRequest<T>(request: RCPRequest,
mapping: #escaping (JSON) throws -> T,
completion: #escaping (T?, Error?) -> Void) {
let batch = batchFactory.create(request)
let httpRequest = MyServiceRequest(batch: batch)
Session.send(httpRequest){ result in
switch result {
case .success(let auth):
let gson = JSON(auth)
do {
let output = try mapping(gson)
completion(output, nil)
} catch {
completion(nil, error)
}
case .failure(let error):
completion(nil, error)
}
}
}
Then call it like this,
let request1 = RPCRequest(params: SomeParams)
sendRequest(request: request1,
mapping: { json in
// convert from json to the custom type T, whatever T is
// throw error if something isnt right in json
},
completion: { output, error in
if let output = output {
}
})

Related

Can't access decoded JSON data (from API) outside the URLSession data task Function Scope

I am facing an issue when trying to retrieve and decode JSON data from an API. I am able to get the data from the API and decode it successfully, but I can't access it from outside the function scope. I am using the following function to get and decode data. The API returns an array of JSON objects.
func getJSON(completed: #escaping () -> ()) {
var jsonData = [API_data] () // A struct for retrieved data
let url = "URL Here" // I have the original URL here, which I can't share
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self]data,response,error in
guard let data = data, error == nil else{
print("error")
return
}
do{
jsonData = try JSONDecoder().decode([API_data].self,from:data)
} catch{
print(error)
}
print(jsonData[0].id) // This prints my data
})
print(jsonData[0].id) // This won't print my data
task.resume()
}
struct API_data: Codable {
let id : String
}
As a result of this, I cannot use this data anywhere in the application. Any help will be appreciated. I have also tried making jsonData a global variable, updating it in the function and returning it and then using it, still doesn't work.
Thanks for your help.
As you already have a completion handler use it and pass the received data
func getJSON(completed: #escaping ([API_data]) -> Void) {
let url = "URL Here" // I have the original URL here, which I can't shar
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _ , error in
if let error = error { print(error); return }
do {
completed(try JSONDecoder().decode([API_data].self,from:data!))
} catch{
print(error)
}
}
task.resume()
}
and use it
getJSON { apiData in
print(apiData[0].id) // This prints my data
}
Or more comfortable with the Result type
func getJSON(completed: #escaping (Result<[API_data],Error>) -> Void) {
let url = "URL Here" // I have the original URL here, which I can't shar
let task = URLSession.shared.dataTask(with: URL(string: url)!) { data, _ , error in
if let error = error { completed(.failure(error)); return }
Result { try JSONDecoder().decode([API_data].self,from: data!) }
}
task.resume()
}
getJSON { result in
switch result {
case .success(let apiData): print(apiData[0].id) // This prints my data
case .failure(let error): print(error)
}
}
It is asynchronous, you might want to access it a bit later when it’s ready, e.g. by using the completion handler.
Try creating a completion handler from your API function and send the data with the completion. And try using it there.

Issue To Create A Base Service <Alamofire,PromiseKit,Generic>

Hello everyone I want to crate a base api service for my apps but when I try a lot of different way but i can't solve my problems. I have to use alamofire, promiseKit and generic's.
My first solution and his issue: I can get my json from api but when I try to decode my json every time it fails. There is the code:
func fetchData<T: Codable>(_ page: Int) -> Promise<T> {
let path = getPath(page)
return Promise<T> { seal in
AF.request(path, method: .get).validate(statusCode: 200..<300).responseString { response in
switch response.result {
case .success(let data):
if let json = data.data(using: .utf8) {
do {
let res = try JSONDecoder().decode(T.self, from: json)
print(res)
seal.fulfill(res)
} catch {
print("json serilaztion error")
print(error)
seal.reject(error)
}
}
case .failure(let error):
seal.reject(error)
}
}
}
}
My second solution and issue : When I do my request I get my json response and decode it but in this solution I can't return data with seal.fulfill(data) gives me "Cannot convert value of type 'Any' to expected argument type 'T'" and when I try to seal.fulfill(data as! T) like this when I run app always crashes. There is my code :
func fetchData<T: Codable>(_ page: Int) -> Promise<T> {
let path = getPath(page)
return Promise<T> { seal in
AF.request(path, method: .get).validate(statusCode: 200..<300).responseJSON { response in
switch response.result {
case .success(let data):
seal.fulfill(data as! T) // Cannot convert value of type 'Any' to expected argument type 'T'
case .failure(let error):
seal.reject(error)
}
}
}
}
Finally I'm using test api https://www.flickr.com/services/api/explore/flickr.photos.getRecent to test my code. How I can solved this issue's ? Thanks for your helps
Use responseDecodable instead of responseString or responseJSON.
AF.request(...).responseDecodable(of: T.self) { response in
...
}

Swift Searching For Values inside values of Dictionary

I have a URL which my app fetches. it prints a dictionary with two keys but inside one of the keys is a lot of information I would like to get for my app.
The URL gets lots of information but not as a conventional dictionary.
this is a VERY simplified version:
["person":
name: John
height: 187, "fruit": colour: red
]
etc...
so I would just want to get the name of the person inside the key person but I am having trouble finding this.
Is there any way to do this? I have been trying JSON Parsing, for loops and I am stuck.
Edit:
it isn't a dictionary inside a dictionary. If you would like to see what I am working with. Just copy and paste this link. It is an example of what I am using. http://itunes.apple.com/lookup?bundleId=com.burbn.instagram
I would need just the seller name or just the currency etc.
Code to read the link and print it:
override func viewDidLoad() {
super.viewDidLoad()
fetchData { (dict, error) in
print(dict!)
}
}
func fetchData(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = URL(string: link)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any]{
completion(array, nil)
}
} catch {
print(error)
completion(nil, error)
}
}
task.resume()
}
The data you are fetching is JSON. In order to use it, you will have to decode it. The recommended way is using JSONDecoder in Swift.
First you will have to define your model, which correspond to the data model, and make it conform to Codable protocol:
struct App: Codable {
var sellerName: String
// Alternatively, if you don't want to use an enum, you can use a String.
var currency: Currency
enum Currency: String, Codable {
case australianDollar = "AUD",
case britishPound = "GBP",
case euro = "EUR",
case hongKongDollar = "HKD",
case usDollar = "USD"
// Complete this with all the currency…
}
}
struct JSONResult: Codable {
var resultCount: Int
var results: [App]
}
Once this is done, you only have to edit your fetchData method so it returns an array App populated with the data you fetched.
Swift 4 version:
func fetchData(completion: #escaping (JSONResult?, Error?) -> Void) {
guard let url = URL(string: link) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(nil, error)
return
} else if let data = data {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(JSONResult.self, from: data)
completion(result, nil)
} catch {
print(error)
completion(nil, error)
}
}
}
task.resume()
}
Swift 5 version using Result type:
func fetchData(completion: #escaping (Result<JSONResult, Error>) -> Void) {
guard let url = URL(string: link) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
completion(.failure(error))
return
} else if let data = data {
do {
let decoder = JSONDecoder()
let result = try decoder.decode(JSONResult.self, from: data)
completion(.success(result))
} catch {
print(error)
completion(.failure(error))
}
}
}
task.resume()
}
More information about JSONDecoder
Dictionary data is:
let dict = ["person": ["name": "John", "height": "187"], "fruit": ["colour": "red"]]
Suppose you need name of the person. So you can do it by the following way.
if let person = dict["person"], let name = person["name"] as? String {
print (name)
}

Struggling with saving data after JSON call

I am trying to save data from a JSON call into a variable (calling this in ViewDidLoad if it makes a difference). I've been stuck on this for quite a while and am getting pretty frustrated.
I know there are topics on this already, but I can't seem to figure it out and I'm hoping theres a simple explanation for what I'm doing wrong.
Edit: The execution goes to "print("breakpoint")" line before doing the JSON call, I'd like to force execution to wait so I can actually fill up wantedCARDSET but am struggling with how to do that. Sorry if that was unclear!
Here's the call in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
setPickerView.dataSource = self
setPickerView.delegate = self
testJSONgrab.getCards(url: "https://mtgjson.com/json/WAR.json") {json, error in
DispatchQueue.main.async {
for item in json {
self.wantedCARDSET.append(item)
}
}
}
print("breakpoint")
getCards function
class mtgJSONDATA {
func getCards(url: String, completionHandler: #escaping ([CARDS], Error?)-> Void) {
var cardSet = [CARDS]()
guard let url = URL(string:url) else {return}
let session = URLSession.shared
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
if error == nil {
guard let data = data else {return}
do {
let decoded = try JSONDecoder().decode(mtgJSON.self, from: data)
DispatchQueue.main.async {
for item in decoded.cards! {
cardSet.append(item)
}
completionHandler(cardSet, nil)
}
} catch let jsonError {
print("Error serializing JSON: ", jsonError)
}
}
})
task.resume()
}
You miss a reload
DispatchQueue.main.async {
self.wantedCARDSET = item
self.setPickerView.reloadAllComponents()
}
As the call to get your data is asynchronous

Getting a JSON from the Task

So I know how to parse JSON and retrieve a JSON from a URLRequest. What my objective is to remove this JSON file so I can manipulate it into different UIViewControllers. I have seen some stuff with completion handlers but I run into some issues, and I haven't fully understand. I feel like there is a simple answer, I am just being dumb.
How can I take this JSON outside the task and use it in other Swift files as a variable?
class ShuttleJson: UIViewController{
func getGenres(completionHandler: #escaping (_ genres: [String: Any]) -> ()) {
let urlstring = "_________"
let urlrequest = URLRequest(url: URL(string: urlstring)!)
let config = URLSessionConfiguration.default
let sessions = URLSession(configuration: config)
// request part
let task = sessions.dataTask(with: urlrequest) { (data, response, error) in
guard error == nil else {
print("error getting data")
print(error!)
return
}
guard let responseData = data else {
print("error, did not receive data")
return
}
do {
if let json = try JSONSerialization.jsonObject(with: responseData, options: []) as? [String: Any]{
//Something should happen here
}
print("no json sucks")
}
catch{
print("nah")
}
}
task.resume()
}
}
First of all remove the underscore and the parameter label from the completion handler. Both are useless
func getGenres(completionHandler: #escaping ([String: Any]) -> ()) {
Then replace the line
//Something should happen here
with
completionHandler(json)
and call the function
getGenres() { json in
print(json)
}
Notes:
The check guard let responseData = data else is redundant and it will never fail. If error is nil then data is guaranteed to have a value.
You should print the caught error rather than a meaningless literal string.