How can I parse JSON from a rest API in swift - swift

I'm modifying code from 'Hacking with swift' Project 7 to take a JSON file using an API and placing it in a table view
I'm at a bit of loss of what to do next, tried moving around the call to the parse function and using the commented out code
override func viewDidLoad() {
super.viewDidLoad()
let username = "UserName"
let password = "Password"
let loginData = String(format: "%#:%#", username,
password).data(using: String.Encoding.utf8)!
let base64LoginData = loginData.base64EncodedString()
let url = URL(string: "......")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Basic \(base64LoginData)", forHTTPHeaderField:
"Authorization")
let task = URLSession.shared.dataTask(with: request) { data,
response,
error in
guard let data = data, error == nil else {
print("error")
return
}
if let httpStatus = response as? HTTPURLResponse {
parse(json: data)
print("status code = \(httpStatus.statusCode)")
}
}
task.resume()
}
// if let url = URL(string: urlstring){
// if let data = try? Data(contentsOf: url){
// parse(json: data)
// return
// }
// }
// showError()
//}
func parse(json: Data) {
let decoder = JSONDecoder()
if let jsonPetitions = try? decoder.decode(Petitions.self, from:
json) {
petitions = jsonPetitions.results
tableView.reloadData()
}
}
I receive a status code of '200' so I know the API call works fine.
The issue seems to be with calling the parse function I get the
following error "Call to method 'parse' in closure requires explicit
'self.' to make capture semantics explicit"

1- The error means to add self here
self.parse(json: data)
2- You should reload the table in main thread as callback of URLSession.shared.dataTask runs in a background thread to avoid un-expected results/crashes
DispatchQueue.main.async {
self.tableView.reloadData()
}

Related

POST to Web API always returns nil

I'm just learning Web APIs with Swift.
And I've done as follows
Make some basic configuration
let session = URLSession(configuration: .default)
// Prepare URL #the URL is virtual now
let url = URL(string: "http://something.com/api")
guard let requestUrl = url else { fatalError() }
// Prepare URL Request Object
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
Set Post parameter
let parameters: [String: String] = [
"user_ID": "1",
]
let jsonData = try JSONEncoder().encode(parameters)
request.httpBody = jsonData
Take request to our web api
// Perform HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
// Check for Error
if let error = error {
print("Error took place \(error)")
return
}
guard let data = data else { return }
do {
let myEnterprise = try JSONDecoder().decode(Enterprise.self, from: data)
print("Response data:\n \(myEnterprise)")
print("Response data:\n \(myEnterprise.returnData)")
} catch let jsonErr {
print(jsonErr)
}
}
task.resume()
myEnterprise is always nil. Can anyone help me?

Swift - Reusable URL Request with Delegates

Hi I'm new to Swift and I am trying to create a reusable generic Download Manager for URL Request that can be reused throughout my project in different View Controllers or reused within the same VC for a different URL Request calls. The problem that I have is how do I pass the Data Type from the Request into the Download Manager and then return the Downloaded Data back to the VC with the corresponding Data Type. I am able to pass the Data Type in a call to downloadRequest but I can't figure out how to pass the Data Type back to the VC via a delegate DownloadManagerDelegate. Any help would be greatly appreciate it!
Generic Download Manager:
protocol DownloadManagerDelegate {
func didUpdateData<T: Codable>(modelType: T.Type, downloadedData: T.Type)
}
struct DownloadManager {
var delegate: DownloadManagerDelegate?
func downloadRequest<T: Codable>(modelType: T.Type, parameters: [String: Any]) {
guard let url = URL(string: "https://www.someAPI...") else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
guard let httpBodyWithParameters = try? JSONSerialization.data(withJSONObject: parameters, options: []) else
{
print("error")
return
}
request.httpBody = httpBodyWithParameters
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if error != nil {
print("error")
return
}
if let safeData = data {
if let downloadedData = parseDownloadedData(data: safeData) {
self.delegate?.didUpdateData(modelType: modelType, downloadedData: downloadedData)
}
}
}.resume()
func parseDownloadedData(data: Data) -> T?{
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(T.self, from: data)
return decodedData
} catch {
print(error)
return nil
}
}
}
Delegate in my VC:
override func viewDidLoad() {
super.viewDidLoad()
downloadManager.delegate = self
}
func didUpdateData(modelType: modelType,downloadedData:downloadedData){
DispatchQueue.main.async {
print(downloadedData)
}
}
To call download downloadRequest:
downloadManager.downloadrequest(modeType: Type1.self, parameters: parameters)
The Data Model is defined as a struct:
struct DataModel1: Codable {
let ItemID: String
}
Then in the same VC I call the same function downloadManager that will call a different API which should return data for a different Model Type (defined as Struct)
downloadManager.downloadRequest(modeType: Type2.self, parameters: parameters)
The Data Model is defined as a struct:
struct DataModel2: Codable {
let EmployeeeID: String
}
In the Swift times Protocol/Delegate smells a bit objective-c-ish.
I recommend a completion handler with the versatile Result type.
It returns the generic type non-optional on success and any error on failure.
The force unwrapping of data is safe because if error is nil then data has a value
struct DownloadManager {
func downloadRequest<T: Decodable>(modelType: T.Type, parameters: [String: Any], completion : #escaping (Result<T, Error>) -> Void) {
guard let url = URL(string: "https://www.someAPI...") else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
let httpBodyWithParameters = try JSONSerialization.data(withJSONObject: parameters)
request.httpBody = httpBodyWithParameters
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let error = error {
completion(.failure(error))
} else {
completion( Result { try JSONDecoder().decode(T.self, from: data!)})
}
}.resume()
} catch {
completion(.failure(error))
}
}
}
And use it
downloadManager.downloadrequest(modeType: Type1.self, parameters: parameters) { result in
switch result {
case .success(let data): print(data)
case .failure(let error): print(error)
}
}

Swift "expected to decode Array<Any> but found a dictionary instead."

I am using this code to fetch data from an API and setting some variables in my view controller and everything works fine. Since I am doing this in multiple controllers I am attempting to refactor the network call into a Service class using a completion handler to execute the necessary code for settings the view's variables. The service works fine on some of the controllers, but any controller that attempts to decode an array of objects, I get the error
typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
Working
let httpURL = "https://myurl.com/api"
guard let url = URL(string: httpURL) else { return }
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// Setting other headers here
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
do {
let consults = try JSONDecoder().decode([Consultation].self, from:data)
DispatchQueue.main.async {
// Settings values here
self.consultationTableView.reloadData()
}
} catch let jsonErr {
print(jsonErr)
}
}
task.resume()
}
Not Working
struct Service {
static let sharedInstance = Service()
func fetchPage(url: String, completion: #escaping (Data) -> ()){
let httpURL = "https://myurl.com/api"
guard let url = URL(string: httpURL) else { return }
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// Other headers here
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
completion(data)
}
task.resume()
}
}
ViewController
let httpURL = "https://myUrl.com/api"
Service.sharedInstance.fetchPage(url: httpURL) { (data) in
do {
let consults = try
---> Error Happens>> JSONDecoder().decode([Consultation].self, from:data) }
} catch let jsonErr {
print(jsonErr)
}
}
Any idea why when using the service class, the error shows up?

Get request return error code 405 - method not allowed

I've wrote a function for GET request from rest and it says that i have method not allowed - code 405 which is werid and i can not find solution for that.
I am doing GET via current token which was assigned to the user after logged in.
Could someone have a look on the code and tell me what might be wrong ?
func getRequest() -> Void {
let json: [String: Any] = ["token": SessionMenager.Instance.token]
let jsonData = try? JSONSerialization.data(withJSONObject: json)
// create post request
let url = URL(string: MY_URL)!
var request = URLRequest(url: url)
request.httpMethod = "GET"
// insert json data to the request
request.httpBody = jsonData
request.setValue("application/json;charest=utf-8", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
if let httpResponse = response as? HTTPURLResponse {
print("GET : code - \(httpResponse.statusCode)")
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
} else{
print(error.debugDescription)
}
}
task.resume()
}
Thanks in advance!!

URL request using Swift

I have access the "dictionary" moviedb for
example : https://www.themoviedb.org/search/remote/multi?query=exterminador%20do%20futuro&language=en
How can i catch only the film's name and poster from this page to my project in Swift ?
It's answer :)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
reload()
}
private func reload() {
let requestUrl = "https://www.themoviedb.org/search/remote/multi?query=exterminador%20do%20futuro&language=en"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let request = NSURLRequest(URL: NSURL(string: requestUrl)!)
let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
if let error = error {
println("###### error ######")
}
else {
if let JSON = NSJSONSerialization.JSONObjectWithData(data,
options: .AllowFragments,
error: nil) as? [NSDictionary] {
for movie in JSON {
let name = movie["name"] as! String
let posterPath = movie["poster_path"] as! String
println(name) // "Terminator Genisys"
println(posterPath) // "/5JU9ytZJyR3zmClGmVm9q4Geqbd.jpg"
}
}
}
})
task.resume()
}
}
You need to include your api key along with the request. I'd just try something like this to see if it works or not. If it does, then you can go about using the api key in a different way to make it more secure. I wouldn't bother as it's not an api with much sensitive functionality.
let query = "Terminator+second"
let url = NSURL(string: "http://api.themoviedb.org/3/search/keyword?api_key=YOURAPIKEY&query=\(query)&language=‌​en")!
let request = NSMutableURLRequest(URL: url)
request.addValue("application/json", forHTTPHeaderField: "Accept")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
if let response = response, data = data {
print(response)
//DO THIS
print(String(data: data, encoding: NSUTF8StringEncoding))
//OR THIS
if let o = NSJSONSerialization.JSONObjectWithData(data, options: nil, error:nil) as? NSDictionary {
println(dict)
} else {
println("Could not read JSON dictionary")
}
} else {
print(error)
}
}
task.resume()
The response you'll get will have the full list of properties. You need the poster_path and title (or original_title) property of the returned item.