no data returned from URLSession - json

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
}
}

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)")
}

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

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()
}

Swift: Loading JSON data from a URL

I am attempting to modify Apple's code from the Landmarks tutorial to load JSON data from a remote URL. The url is a php script which returns plaintext JSON data.
Apple's code:
func loadLocalJSON<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) from main bundle:\n\(error)")
}
}
And my code as currently modified:
func loadRemoteJSON<T: Decodable>(_ urlString: String) -> T {
let data: Data
guard let url = URL(string: urlString) else {
fatalError("Invalid URL")
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
fatalError(error?.localizedDescription ?? "Unknown Error")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data) // <--ERROR HERE
} catch {
fatalError("Couldn't parse data from \(urlString)\n\(error)")
}
}
}
The error I am getting is Unexpected non-void return value in void function
I thought the function was supposed to be returning an instance of T. Where am I going wrong?
You need a completion block instead of a return type. You are doing the async task. URLSession.shared.dataTask is async type.
func loadRemoteJSON<T: Decodable>(_ urlString: String, completion: #escaping ((T) -> Void)) {
let data: Data
guard let url = URL(string: urlString) else {
fatalError("Invalid URL")
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
fatalError(error?.localizedDescription ?? "Unknown Error")
}
do {
let decoder = JSONDecoder()
let data = try decoder.decode(T.self, from: data)
completion(data)
} catch {
fatalError("Couldn't parse data from \(urlString)\n\(error)")
}
}
}
Usage:
struct TestModel: Decodable {
var name: String
}
loadRemoteJSON("urlstring") { (data: TestModel) in
print(data.name)
}
If you are using Swift 5.5 then you can return your data by using Async Await. There many articles and videos are available on the net. You can check this.
func loadRemoteJSON<T: Decodable>(_ urlString: String,completion: #escaping (T)->Void)
Using the completion block is simple, just decode the JSON and pass it into your completion handler.
let decodedJSON = try decoder.decode(T.self, from: data)
completion(decodedJSON)
Now, when you call loadRemoteJSON, you'll be able to access the JSON inside of the function's completion block, like:
loadRemoteJSON(urlString: someString){ data in
//data is your returned value of the generic type T.
}

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()
}
}

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!?