How can i get authorization to retrieve data from API Swift? - json

I'm trying to retrieve some data from an API, but I got an error: "The given data was not valid JSON ", Status code: 401
I think that is an authentication problem. How can I set the auth credentials to make the GET request?
This is the code for retrieving the data from the JSON.
func loadData()
{
guard let url = URL(string: getUrl) else { return }
URLSession.shared.dataTask(with: url) { data, res, err in
do {
if let data = data {
let result = try JSONDecoder().decode([ItemsModel].self, from: data)
DispatchQueue.main.async {
self.items = result
}
} else {
print(" No Data ")
}
} catch( let error)
{
print(res)
print(String(describing: error))
}
}.resume()
}
This is the code for the view :
struct GetView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
NavigationView {
VStack {
List(viewModel.items, id: \.id) { item in
Text(item.year)
}
} .onAppear(perform: {
viewModel.loadData()
})
.navigationTitle("Data")
}
}
}

To handle authentication you must implement a delegate for your URLSession:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
completionHandler(.performDefaultHandling, challenge.proposedCredential)
}
However, your 401 error may be due to your code not sending a valid GET request to the server. You probably want to decode the 'res' value to determine the status code:
if let response = res as? HTTPURLResponse {
if response.statusCode != 200 {
// data may be JSON encoded but you should get some for
// statusCode == 401
}
}
Without knowing the kind of service you are connecting to it is hard to speculate if you need a GET or a POST. The URL you use may require a query parameter.

I found the solution. This is the code for Basic Auth :
func loadData() {
//Credentials
let username = ""
let password = ""
let loginString = "\(username):\(password)"
let loginData = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString()
//Request
guard let url = URL(string: getUrl) else {return}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
//Setup Session
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let data = data {
print(String(data: data, encoding: .utf8)!)
let result = try JSONDecoder().decode([ItemsModel].self, from: data)
DispatchQueue.main.async {
self.items = result
}
}
else {
print(" No Data ")
}
} catch( let error)
{
print(String(describing: error))
}
}
task.resume()
}

Related

SwiftUI URLSession JSONDecoder returning error when trying to parse nested Json

I am trying to parse a nested Json in SwiftUI for the last couple of days and I have no idea how to move forward.
At this point, I suspect that the trouble is a parameter received within the Json named "data" which might cause a confusion between the param value in struct "VTResponse" and the data param that URLSession.shared.dataTask is getting.
Here's the code at this point:
import UIKit
struct VTResponse: Decodable {
let data: [VT]
}
struct VT: Decodable {
var id: String
}
let token = "<TOKEN>"
let XDOMAIN = "<XDOMAIN>"
guard let url = URL(string: "https://www.lalalla.com/subdomains") else {
fatalError("Invalid URL")
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("x-apikey: \(token)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { return }
let result = try? JSONDecoder().decode(VTResponse.self, from: data)
if let result = result {
result.data.forEach {
print($0.id)
}
}
else {
print("Error")
}
}.resume()
Assuming that I define a token and domain for the query, for example, lookup all of the subdomains of "giphy.com", the Json response:
Json Response - Pastebin
As you can see in the Json response, the subdomains parameter ("id") is under a dictionary, under an array("data"). My guess is the code is trying to assign data to the variable:
guard let data = data, error == nil else { return }
But this is just a guess. And even if so, how could I solve this?
Anyways, I'm getting the following output:
Error
I'm trying to get the following output:
pingback.giphy.com
media3.giphy.com
api.giphy.com
developers.giphy.com
media.giphy.com
x-qa.giphy.com
media1.giphy.com
x.giphy.com
media4.giphy.com
media0.giphy.com
Any ideas?
try this using an x-apikey header for your token:
func fetchData(token: String, XDOMAIN: String, completion: #escaping (VTResponse) -> Void) {
guard let url = URL(string: "https://www.virustotal.com/api/v3/domains/\(XDOMAIN)/subdomains") else {
fatalError("Invalid URL")
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "x-apikey") // <-- here
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return } // todo return some error msg
do {
let results = try JSONDecoder().decode(VTResponse.self, from: data)
return completion(results)
} catch {
print(error) // <-- here important
}
}.resume()
}
And use it like this:
fetchData(token: "xxx", XDOMAIN: "www") { results in
results.data.forEach {
print("---> id: \($0.id)")
}
}
EDIT-1: to get an array of [String], that is, of id, use this function:
func ListFromSubDomains(token: String, XDOMAIN: String, completion: #escaping ([String]) -> Void) {
fetchData(token: token, XDOMAIN: XDOMAIN) { results in
completion(results.data.map{ $0.id })
}
}
and use it like this:
var IPList: [String] = []
ListFromSubDomains(token: "xxx", XDOMAIN: "www") { ids in
IPList = ids
print("---> ids: \(ids)")
}

Twice JSON Request instead of one

My JSON request works fine but every time I do a request the network monitor shows me there are two requests made. What is wrong here? How can I optimize it to just one?
I tried to find the reason for this and it looks like it has to happen here with this request.
struct networkGoogleApi {
static var testVariable = [Items]()
static var gewählteKategorie = 0
mutating func fetchPoi(geoCordinate: String) {
var baseUrlToApiConnection = ""
baseUrlToApiConnection = {
switch networkGoogleApi.gewählteKategorie {
case 0:
return
"first url"
case 1:
return "other url"
default:
return "error"
}
}()
let urlString = "\(baseUrlToApiConnection)?at=\(geoCordinate)"
print(urlString)
performRequest(urlString: urlString)
}
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
print(error!)
return
}
if let safeData = data {
parseJSON(poiData: safeData)
}
}
//4. Start the task
task.resume()
}
}
}
func parseJSON(poiData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(POIData.self, from: poiData)
networkGoogleApi.testVariable = decodedData.items
//print(networkGoogleApi.testVariable)
} catch {
print(error)
}
}
Breakpoint Event added.

Issue with Codable object to JSON conversion

Apologies if this is a basic question, I am new using Apis and JSON in Swift. I am attempting to submit a post request but am receiving:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__SwiftValue)'.
I believe that this is due to a incorrect/unconvertible type but I have tried multiple different permutations of the variables I am passing and it continues to fail.
Here is my ContentView:
struct ContentView: View {
#State var town: Space = Space(title: "test city", description: "is this working")
var body: some View {
Button(action: {
Api.postRequest(param: ["space" : town], urlString: Api.spacePostUrl) { (update) in
print("\(update)")
}
}) {
Text("Post Request")
}
}
}
The underlying data struct:
struct Space: Codable {
var title: String
var description: String
}
And my attempted API call:
class Api {
static let spacePostUrl = "http://localhost:3001/spaces"
static let spaceGetUrl = "http://localhost:3001/"
static func postRequest(param: [String : Codable], urlString: String, completion: #escaping (Int) -> ()) {
guard let url = URL(string: urlString) else { return }
let body = try? JSONSerialization.data(withJSONObject: param)
var request = URLRequest(url: url)
request.httpBody = body
request.httpMethod = "POST"
URLSession.shared.dataTask(with: request) { (data, request, error) in
guard let update = data else { return }
do {
let update = try JSONDecoder().decode(Int.self, from: update)
DispatchQueue.main.async {
completion(update)
}
}
catch {
print(error)
}
}
}
}
class Api {
static let spacePostUrl = "http://localhost:3001/spaces"
static let spaceGetUrl = "http://localhost:3001/"
static func postRequest(param: [String : Codable], urlString: String, completion: #escaping (Int) -> ()) {
guard let url = URL(string: urlString) else { return }
let body = try? JSONSerialization.data(withJSONObject: param)
var request = URLRequest(url: url)
request.httpBody = body
request.httpMethod = "POST"
let task = URLSession.shared.dataTask(with: request) { (data, request, error) in
guard let update = data else { return }
do {
let update = try JSONDecoder().decode(Space.self, from: update)
DispatchQueue.main.async {
completion(update)
}
}
catch {
print(error)
}
}
task.resume()
}
}

no data returned from URLSession

I performed this same request on Postman, and it works fine. However here the data is empty when I run this URLSession (see DATA EMPTY tag):
import Foundation
struct PhotoResponse: Decodable {
let results: [Photo]
}
class PhotoInteractor {
static var shared = PhotoInteractor()
var error: Error?
func getPhotos(query: String, completionHandler: #escaping ([Photo], Error?) -> Void) {
guard let url = URL(string: "https://api.unsplash.com/search/photos?query=o&page=1&per_page=30&") else {
return
}
var request = URLRequest(url: url)
request.setValue("Client-ID \(Config.shared.accessKey)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
//*** DATA EMPTY!!! ***
if let data = data, let photos = self.photosFromJSONResponse(data) {
completionHandler(photos, nil)
}
}.resume()
}
func photosFromJSONResponse(_ data: Data) -> [Photo]? {
do {
let photoResponse = try JSONDecoder().decode(PhotoResponse.self, from: data)
return photoResponse.results
} catch {
self.error = error
}
return nil
}
}

The data could not be read because it isn't in the correct format JSON & SWIFT 3

Here is Stripe's example code for retrieving a customer (https://github.com/stripe/stripe-ios/blob/master/Example/Stripe%20iOS%20Example%20(Simple)/MyAPIClient.swift):
#objc func retrieveCustomer(_ completion: #escaping STPCustomerCompletionBlock) {
guard let key = Stripe.defaultPublishableKey() , !key.contains("#") else {
let error = NSError(domain: StripeDomain, code: 50, userInfo: [
NSLocalizedDescriptionKey: "Please set stripePublishableKey to your account's test publishable key in CheckoutViewController.swift"
])
completion(nil, error)
return
}
guard let baseURLString = baseURLString, let baseURL = URL(string: baseURLString) else {
// This code is just for demo purposes - in this case, if the example app isn't properly configured, we'll return a fake customer just so the app works.
let customer = STPCustomer(stripeID: "cus_test", defaultSource: self.defaultSource, sources: self.sources)
completion(customer, nil)
return
}
let path = "/customer"
let url = baseURL.appendingPathComponent(path)
let request = URLRequest.request(url, method: .GET, params: [:])
let task = self.session.dataTask(with: request) { (data, urlResponse, error) in
DispatchQueue.main.async {
let deserializer = STPCustomerDeserializer(data: data, urlResponse: urlResponse, error: error)
if let error = deserializer.error {
completion(nil, error)
return
} else if let customer = deserializer.customer {
completion(customer, nil)
}
}
}
task.resume()
}
Stripe has a customer deserializer that specifies that "STPCustomerDeserializer expects the JSON response to be in the exact same format as the Stripe API." The Stripe API is here in Nodejs:
// Retrieve Stripe Customer
app.get('/customer', function(request, response) {
// Load the Stripe Customer ID for your logged in user
var customer = 'cus_abc...';
stripe.customers.retrieve(customerId, function(err, customer) {
if (err) {
response.status(402).send('Error retrieving customer.');
} else {
response.json(customer);
}
});
The response I get is an error: The data could not be read because it isn't in the correct format. I think it wants me to return JSON but I tried that several different ways to no avail. Such as:
func retrieveCustomer(_ completion: #escaping STPCustomerCompletionBlock) {
guard let key = Stripe.defaultPublishableKey() , !key.contains("#") else {
let error = NSError(domain: StripeDomain, code: 50, userInfo: [
NSLocalizedDescriptionKey: "Set PubKey"])
completion(nil, error)
return
}
guard let baseURLString = baseURLString, let baseURL = URL(string: baseURLString) else {
let customer = STPCustomer(stripeID: "", defaultSource: self.defaultSource, sources: self.sources)
completion(customer, nil)
return
}
let path = "/customer"
let url = baseURL.appendingPathComponent(path)
let request = URLRequest.request(url, method: .GET, params: [:])
let task = self.session.dataTask(with: request) { (data, urlResponse, error) in
DispatchQueue.main.async {
let deserializer = STPCustomerDeserializer(data: data, urlResponse: urlResponse, error: error)
if let error = deserializer.error {
completion(nil, error)
return
} else if let customer = deserializer.customer {
do {
let parser = try JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as Any
print(parser)
} catch {
print(error)
}
completion(customer, nil)
}
}
}
task.resume()
}
What am I missing!?