Swift- JSON Requests with more than one page - json

I am using Alamofire and SwiftyJSON to access Youtube's API and get a channels's playlist count. My problem is that Youtube returns only 5 items per page (or request) and I don't know how to access the rest!
func getPlaylists() {
Alamofire.request(.GET, playlistURL).validate().responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
print(json["items"].count) //Always returns 5
}
case .Failure(let error):
print(error)
}
}
}
Can anybody let me know how to access the next set of results?

You'll find the answer in the API documentation, possibly here YouTube Data API.
Note: in your request, you can set the max items per page with a query parameter: maxResults. This can be 5 to 50, default equals 5.
When you get a response, the server returns a JSON containing a JSON value "nextToken": <string>. Use this value in your next request, where you insert a query parameter pageToken=<string>.
You might modify your getPlaylists method so that it takes a parameter nextToken: String. Initially call this method with an empty string or better an optional initialized to nil.
In your completion handler, retrieve the "nextToken" value from the JSON response. If it exists, call your method with this parameter. Otherwise, you got all pages.
Your method getPlaylists should also have a completion handler. Call the completion handler when all pages have been retrieved.

Related

GoLang parse inconsistent JSON

I am currently writing an application that queries a third party API. Previously when I have done this, I have done it the correct way and created a struct and unmarshalled the response string into the struct and accessed all the data that way. However that only works if the data structure is consistent.
I have the issue of trying to query an API where the structure is inconsistent. If the request was successful I get the response
{'status': 'ok', 'due_date': '2023-01-01', 'library': 'AIEHA1'}
but if it's unsuccessful, depending on the error type, I get different structures; some examples:
{'status': 'unauthorized', 'error': 'Field Bearer empty'}
{'status': 'not-found', 'error-details': {'type': 'file-not-found', 'file': '/index'}}
Obviously, I can pass this into a generic map but I was wondering what the proper practice is for something like this? Yes the API is terrible, yes sadly I have to use it.
One way of dealing with this is to have a structure containing all possible fields:
type apiResult struct {
Status string
DueDate string
Error string
ErrorDetails ErrorDetail
}
You unmarshal the API response, then process the API result structure to determine the actual return type.
Another way of doing it is to unmarshal into the expected struct, and then unmarshal again into an error struct based on status:
json.Unmarshal(result,&data)
if data.Status=="error" {
json.Unmarshal(result,&errorStruct)
} else if data.Status=="not-found" {
... etc.
}
When the response is not an error, this has the benefit of unmarshalling only once to the target struct.
Usually APIs return HTTP status other than 2xx code when there is an error. If that is the case, you can look at the response code and unmarshal based on that.

Could not cast value of type '__NSDictionaryI'

I m using this code to call my rest web service.
But if I try to decode the result of web service call I received error.
class func callPostServiceReturnJson(apiUrl urlString: String, parameters params : [String: AnyObject]?, parentViewController parentVC: UIViewController, successBlock success : #escaping ( _ responseData : AnyObject, _ message: String) -> Void, failureBlock failure: #escaping (_ error: Error) -> Void) {
if Utility.checkNetworkConnectivityWithDisplayAlert(isShowAlert: true) {
var strMainUrl:String! = urlString + "?"
for dicd in params! {
strMainUrl.append("\(dicd.key)=\(dicd.value)&")
}
print("Print Rest API : \(strMainUrl ?? "")")
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 120
manager.request(urlString, method: .get, parameters: params)
.responseJSON {
response in
switch (response.result) {
case .success:
do{
let users = try JSONDecoder().decode(OrderStore.self, from: response.result.value! as! Data)
}catch{
print("errore durante la decodifica dei dati: \(error)")
}
if((response.result.value) != nil) {
success(response as AnyObject, "Successfull")
}
break
case .failure(let error):
print(error)
if error._code == NSURLErrorTimedOut {
//HANDLE TIMEOUT HERE
print(error.localizedDescription)
failure(error)
} else {
print("\n\nAuth request failed with error:\n \(error)")
failure(error)
}
break
}
}
} else {
parentVC.hideProgressBar();
Utility.showAlertMessage(withTitle: EMPTY_STRING, message: NETWORK_ERROR_MSG, delegate: nil, parentViewController: parentVC)
}
}
This is the error that I can print:
Could not cast value of type '__NSDictionaryI' (0x7fff86d70b80) to 'NSData' (0x7fff86d711e8).
2021-09-27 16:34:49.810245+0200 ArrivaArrivaStore[15017:380373] Could not cast value of type '__NSDictionaryI' (0x7fff86d70b80) to 'NSData' (0x7fff86d711e8).
Could not cast value of type '__NSDictionaryI' (0x7fff86d70b80) to 'NSData' (0x7fff86d711e8).
CoreSimulator 732.18.6 - Device: iPhone 8 (6F09ED5B-8607-4E47-8E2E-A89243B9BA90) - Runtime: iOS 14.4 (18D46) - DeviceType: iPhone 8
I generated OrderStore.swift class from https://app.quicktype.io/
//EDIT
.responseJSON returns deserialized JSON, in this case a Dictionary. It cannot be cast to Data what the error clearly confirms.
To get the raw data you have to specify .responseData
Replace
.responseJSON {
response in
switch (response.result) {
case .success:
do {
let users = try JSONDecoder().decode(OrderStore.self, from: response.result.value! as! Data)
with
.responseData {
response in
switch response.result {
case .success(let data):
do {
let users = try JSONDecoder().decode(OrderStore.self, from: data)
Consider that AF 5 supports even .responseDecodable to decode directly into the model
.responseDecodable {
(response : DataResponse<OrderStore,AFError>) in
switch response.result {
case .success(let users): print(users)
Side notes:
As mentioned in your previous question there is no AnyObject in the AF API. The parameters are [String:Any] and responseData is the decoded type. I recommend to make the function generic and use the convenient Result type.
Delete the break statements. This is Swift.
This is an addendum to Vadian's answer. I'm trying to illustrate the process that lead you into this error, with the hopes that you can notice it in the future, before it leads you astray
This is a pretty common "pattern" of error.
Picture it as though you're traversing a maze, starting from some initial data format, and trying to get to some destination data format. At each point along the way, there are several options to choose from, some which get you closer to your goal, and some which lead you further away.
You've chosen to enter the maze at the entryway called responseJSON, whose callback will give you a AFDownloadResponse<Any> (which is the inferred type of the variable you called response).
JSON structures always have an array or dictionary at the top level. Since Alamofire can't statically know which kind of JSON you'll be dealing with, it models this with an Any. At runtime, the type of the Value will be either NSDictionary (or one of its concrete subclasses, like __NSDictionaryI) or NSArray (or one of its concrete subclasses).
You then decide to get the result of that response. Its static type is Result<Any, Error>. You switch over this error, ensuring you're dealing with the success case and not the failure case. Inexplicably, you ignore the payload value associated with the success, but later force unwrap it out with result.response.value!.
result.response.value is an Any, but to placate the compiler your force-cast it to a Data. But we already know this will only ever be an NSArray or NSDictionary, so this will never work.
You could keep wandering around in this area of the maze, and stumble to the end goal via a long path. For example, you could force cast to NSDictionary, then re-serialize that dictionary structure back to a JSON string, which you can turn into Data, only for you to then pass it to JSONDecoder().decode, which will then decode that JSON back. Of course, this is all awfully round-about and wasteful. The issue was the that responseJSON maze entrance was not the right one for where you're trying to go!
You could have instead entered into the responseData maze entrance, which gets you right to your Data destination!
Though you might then realize that the Data was a red herring all along. You didn't actually want Data. You wanted to decode an OrderStore, and Data was how you thought you needed to get there. But it turns out that so many people were entering through the Data entrance with the intent to decode some JSON, that the Alamofire people carved out a new entrance just for you: responseDecodable. It takes you right to the OrderStore, and fiddles around with the JSON, Data or whatever, under the hood where you don't have to worry about it.

How to detect when Alamofire request is null?

I am using Alamofire to do my requests of my API on my Swift application and it works well but I also want to detect when the JSON response is equals to null.
I have tried comparing the response to nil and NSNull but none of those worked for me. I also have tried using JSON.empty but it also does not seem to work. Further, I have created a default option on my switch application trying to catch the options that are not success or failure.
Actually I have only maintained the JSON.empty option but it never enters on else statement.
This is the code that I have right now:
Alamofire.request(encodedUrl!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil).responseJSON { response in
switch(response.result) {
case .success(_):
if let JSON = response.result.value as? [[String : AnyObject]]{
if JSON.isEmpty == false{
//Here the code if the request returns data
}else{
//Here I wanted to use the code if null is retrieved
}
}else{
//The JSON cannot be converted
}
break
case .failure(_):
//Failure
break
}
}
How can I handle null responses on Alamofire?
Thanks in advance!
According to you code, it'll hit the // The JSON cannot be converted since null can't be casted to [[String: AnyObject]].

Alamofire unexpectedly found nil while unwrapping an Optional, yet I can see the JSON

Using AlamoFire I can make an API call to my endpoint, it works and connects as expected. Using the print tools I can print the JSON response to the console and see the JSON string, but I am unable to get this string to move to the next function.
I keep getting the error:
"fatal error: unexpectedly found nil while unwrapping an Optional value (lldb)"
My code looks like this:
func getDataForUser(Username:String, UserToken:String) {
print("Getting data for user \(Username)")
Alamofire.request(.POST, baseURL+userdataURL, parameters: ["Username": Username, "UserToken": UserToken]).response { (req, res, data, error) -> Void in
let jSONResponse: NSDictionary = (try! NSJSONSerialization.JSONObjectWithData(data!,options: NSJSONReadingOptions.MutableContainers)) as! NSDictionary
print(jSONResponse)
if(jSONResponse["Success"] as! Bool == true) {
print("Success! API Request Worked")
self.delegate!.didReceiveAPIResults(jSONResponse)
}
}
}
The error is on line:
self.delegate!.didReceiveAPIResults(jSONResponse)
The console looks like this:
jSONResponse NSDictionary 2 key/value pairs 0x78e8d6a0
[0] (null) "Success" : "1"
[1] (null) "Response" : 2 key/value pairs
The debug screen for jSONResponse gives me data that I can drill into so I know its not nil.
Where is the nil coming from and how do I resolve?
In Swift, "1" is not equal to a Bool value of true.
It's crashing at jSONResponse["Success"] as! Bool == true. You could change this to jSONResponse["Success"] as! String == "1".
If you are able to modify the source of the API you're consuming, you're probably better of making Success a true/false JSON bool value, and leaving your Swift code as is.
My initial thoughts were that the JSON handler or API Controller were wrong.
What I had not passed was the delegate variable in the view controller setup.
EG:
api.delegate = self
Thank you to #paulvs and Eric D for pointing that out.

How to debug network request Swift

I'm making a http request to an API with JSON in the body of the request. I know for a fact that my Dictionary<String, String> containing the JSON data is correct, still I'm getting a response from the server that my input data is not valid. I'm doing something very similar to this: Swift 2.0 url post request. I even tried the extension suggested there but without success.
how should I debug this? I can't find any way to print my whole request to the console. I want to know what my URLRequest actually contains just before I send the request. Also, I want to know that this hex gibberish is actually the right gibberish I meant it to be, how should I do this?
Nothing special about that? Just write an extension and print whatever you want
example:
extension Data {
func toString() -> String? {
return String(data: self, encoding: .utf8)
}
}
extension URLRequest {
func log() {
print("\(httpMethod ?? "") \(self)")
print("BODY \n \(httpBody?.toString())")
print("HEADERS \n \(allHTTPHeaderFields)")
}
}
Usage:
request.log()
Sample log:
POST https://httpbin.org/
BODY
Optional("password=xxx&username=xxx")
HEADERS
Optional(["Content-Type": "application/x-www-form-urlencoded; charset=utf-8"])