I am importing JSON from a link where each file contains a "next" property with a URL of the next JSON file in it, until it is eventually null and has gone through all the files.
My question is, how can I best import all these consecutive files? as they are all required in a table but the limit is 20 objects per JSON as per the API restriction.
I presume the answer would be to do with looping through the results and stating 'if the count of objects is 20 then increment the URL page number by 1'? then once i hit the final page and have 8 results it will know not to go for another loop? I just cant comprehend how this works in code and where it sits.
Current Request:
open class ApiService: NSObject {
open func getData(completionHandler: #escaping (NSDictionary?, NSError?) -> Void) -> Self {
let requestUrl = "https://wger.de/api/v2/exercise/?format=json&language=2&status=2&?limit=199"
Alamofire.request(requestUrl, method: .get, encoding: URLEncoding.default)
.responseJSON { response in
switch response.result {
case .success( let data):
print("Request was sucessful")
completionHandler(data as? NSDictionary, nil)
case .failure(let error):
print("Request failed with error: \(error)")
completionHandler(nil, error as NSError?)
}
}
return self
}
EDIT UPDATE: Had a go at applying the code in the comments, this is my current code but I still have issues:
let requestUrl = "https://wger.de/api/v2/exercise/?format=json&language=2&status=2"
open func getData(_URL: NSURL, completionHandler: #escaping (NSDictionary?, NSError?) -> Void) -> Self {
Alamofire.request(requestUrl, method: .get, encoding: URLEncoding.default)
.responseJSON { response in
switch response.result {
case .success(let data):
print("Request was sucessful")
let json = data as! [String:Any]
let results = json["results"] as! NSDictionary; completionHandler(results, nil)
if let nextURL = json["next"] as? NSURL {self.getData(_URL: nextURL, completionHandler: completionHandler)} else { print(json["next"] as? String)
print("No next page, we are at the end")
}
case .failure(let error):
print("Request failed with error: \(error)")
completionHandler(nil, error as NSError?)
}
}
return self
Updating your Code (I have not compiled it )
open class ApiService: NSObject {
open func getData(requestUrl: String, completionHandler: #escaping (NSDictionary?, NSError?) -> Void) -> Self {
Alamofire.request(requestUrl, method: .get, encoding: URLEncoding.default)
.responseJSON { response in
switch response.result {
case .success(let data):
print("Request was sucessful")
completionHandler(data as? NSDictionary, nil)
case .failure(let error):
print("Request failed with error: \(error)")
completionHandler(nil, error as NSError?)
}
}
return self
}
}
Your Code in View Controller
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let initialURLString = "https://wger.de/api/v2/exercise/?format=json&language=2&status=2"
getApiData(dataURL:initialURLString)
}
func getApiData(dataURL: String) {
let _ = apiService.getData(requestUrl: dataURL) {
(data, error) in
if let data = data {
if let results = data["results"] as? [[String:Any]] {
for result in results {
if let exercise = Exercise(dictionary: result) {
self.exercises.append(exercise)
}
}
self.exercisesTableView.reloadData()
}
if let nextURL = data["next"] as? String
{
print("NEXT URL: \(nextURL)")
self.getApiData(dataURL:nextURL)
}
}
}
}
Related
I have this function:
class func cURL (urlT: String, Completion block: #escaping ((Profile) -> ())) {
GetJson.loadJsonFromUrl(fromURLString: urlT) { (result) in
switch result {
case .success(let data):
//Parse
if let decodedJson = GetJson.ParseJson(jsonData: data) {
block(decodedJson)
}
case .failure(let error):
print("loadJson error:", error)
}
}
}
And that's the function ParseJson, probably to modified too:
class func ParseJson(jsonData: Data) -> Profile? {
do {
let decodedData = try JSONDecoder().decode(Profile.self, from: jsonData)
return decodedData
} catch {
print("decode error: ",error)
}
return nil
}
How can I change the cURL function to return different types of struct, depending on the type of url it receives?
I call cURL this way :
cURL(urlT: encodedUrl) { (Json) in print(Json) }
For exemple here I give cURL a url1 and it returns a Json of type Profile.
What I try to do is, if I give a url2, I would like it to return a Json of type profile2.
I tried to use an enum with types but I can't get it to work.
Any help would be nice. Thanks.
It took me all night but I found a solution to this using generics:
class JSONParser {
typealias result<T> = (Result<T, Error>) -> Void
class func cURL2<T: Decodable>(of type: T.Type,
from url: String,
Completion block: #escaping ((Any) -> ()) ) {
download(of: T.self, from: url) { (result) in
switch result {
case .failure(let error):
print(error)
case .success(let response):
block(response)
}
}
}
class func download<T: Decodable>(of type: T.Type,
from urlString: String,
completion: #escaping result<T>) {
guard let url = URL(string: urlString) else {return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error)
completion(.failure(error))
}
if let data = data {
do {
let decodedData: T = try JSONDecoder().decode(T.self, from: data)
completion(.success(decodedData))
}
catch {
print("decode error: ",error)
}
}
}.resume()
}
}
JSONParser.cURL2(of: Profile.self, from: url1) {(Json) in
print(Json)
}
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)
}
i did read a lot about functions with completion-handler, but now i have a problem how to call this function (downloadJSON) in the correct way. Which parameters do i have to give in the function and handle the result-data (json) in my own class, where the function was called.
This is the code from David Tran. Hi makes wonderful tutorials, but in the code there is no call of this function.
let request: URLRequest
lazy var configuration: URLSessionConfiguration = URLSessionConfiguration.default
lazy var session: URLSession = URLSession(configuration: self.configuration)
typealias JSONHandler = (JSON?, HTTPURLResponse?, Error?) -> Void
func downloadJSON(completion: #escaping JSONHandler)
{
let dataTask = session.dataTask(with: self.request) { (data, response, error) in
// OFF THE MAIN THREAD
// Error: missing http response
guard let httpResponse = response as? HTTPURLResponse else {
let userInfo = [NSLocalizedDescriptionKey : NSLocalizedString("Missing HTTP Response", comment: "")]
let error = NSError(domain: DANetworkingErrorDomain, code: MissingHTTPResponseError, userInfo: userInfo)
completion(nil, nil, error as Error)
return
}
if data == nil {
if let error = error {
completion(nil, httpResponse, error)
}
} else {
switch httpResponse.statusCode {
case 200:
// OK parse JSON into Foundation objects (array, dictionary..)
do {
let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String : Any]
completion(json, httpResponse, nil)
} catch let error as NSError {
completion(nil, httpResponse, error)
}
default:
print("Received HTTP response code: \(httpResponse.statusCode) - was not handled in NetworkProcessing.swift")
}
}
}
dataTask.resume()
}
Let Xcode help you. Type downlo and press return. Xcode completes the function
Press return again and you get the parameters
You have to replace the placeholders with parameter names for example
downloadJSON { (json, response, error) in
if let error = error {
print(error)
} else if let json = json {
print(json)
}
}
Note:
There is a fatal type mismatch error in your code: The result of the JSONSerialization line is [String:Any] but the first parameter of the completion handler is JSON
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.
I found this function to send JSon request and receive the answer
func sendRequest(urlString: String, method: String, completion: #escaping (_ dictionary: NSDictionary?, _ error: Error?) -> Void) {
DispatchQueue.global(qos: .background).async {
var request = URLRequest(url: URL(string:urlString)!)
request.httpMethod = method
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
completion(nil, error)
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
// make error here and then
completion(nil, error)
return
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
DispatchQueue.main.async {
do {
let jsonDictionary:NSDictionary = try JSONSerialization.jsonObject(with: data, options: []) as! [String: Any] as NSDictionary
completion(jsonDictionary, nil)
} catch {
completion(nil, error)
}
}
}
task.resume()
}
}
I need to understand how to call this function correctly. I know urlString and method parameters so the completion and how to return the answer to use it after calling this function.