Instance method requires that 'classname' conform to 'Decodable' - swift

I'm trying to decode a JSON response as a custom type, that I believe conforms to Decodable.
These are the codable structs that I am using
struct ResultSet: Codable {
var name: String
var headers: [String]
var rowSet: [String]
}
struct Scoreboard: Codable {
var resultSets: [ResultSet]
}
And this is the code I'm using to get the JSON from the response
func loadNbaScoreboardData<Scoreboard>() -> Scoreboard {
//var data1: Data
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
let formattedDate = formatter.string(from: Date())
let url = URL(string: "https://stats.nba.com/stats/scoreboard/?GameDate=\(formattedDate)&LeagueID=00&DayOffset=100")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("stats.nba.com", forHTTPHeaderField: "host")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("stats", forHTTPHeaderField: "x-nba-stats-origin")
request.setValue("x-nba-stats-origin", forHTTPHeaderField: "Referer")
var retData: Scoreboard
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else{ return }
do {
let decodedData = try JSONDecoder().decode(Scoreboard.self, from: data)
retData = decodedData
} catch {
fatalError(error)
}
}.resume()
return retData
}
The error I get is Instance method 'decode(_:from:)' requires that 'Scoreboard' conform to 'Decodable'
I'm following the dev documentation here too https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types
What am I doing wrong here?
EDIT: The Scoreboard struct can't be found. I've added the full method

In your code Scoreboard is a generic type (not the concrete type Scoreboard). You can fix the error by adding Codable conformance
func loadNbaScoreboardData<Scoreboard: Codable>() -> Scoreboard {
But the code won't work anyway because you cannot return something from an asynchronous task.
I recommend to make the function async
func loadNbaScoreboardData() async throws -> Scoreboard {
//var data1: Data
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
let formattedDate = formatter.string(from: Date())
let url = URL(string: "https://stats.nba.com/stats/scoreboard/?GameDate=\(formattedDate)&LeagueID=00&DayOffset=100")
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("stats.nba.com", forHTTPHeaderField: "host")
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("stats", forHTTPHeaderField: "x-nba-stats-origin")
request.setValue("x-nba-stats-origin", forHTTPHeaderField: "Referer")
let (data, _ ) = try await URLSession.shared.data(for: request)
return try JSONDecoder().decode(Scoreboard.self, from: data)
}

It was the method declaration
It should be
func loadNbaScoreboardData() -> Scoreboard {
// code
}

Related

Trying to retrieve boolean value from a function in swift

I am trying to get a variable from the function that represents the boolean status of the charge_port_door_open variable, something like this:
let ChargePortStatus = (DATA.response.charge_state.charge_port_door_open)
The function does print a correct boolean, however, 'DATA' cannot be found in scope outside the function.
Edit: Thank you all very much for the help!
This is my first question posted on this website and I wasnt expecting for help to arrive so quickly!
import Foundation
import UIKit
struct Root: Codable {
let response: TEST1
}
struct TEST1: Codable {
let charge_state: TEST2
}
struct TEST2: Codable {
let charge_port_door_open: Bool
}
public func RequestVehicleData() {
let url = URL(string: "https://owner-api.teslamotors.com/api/1/vehicles/:id/vehicle_data")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue( "Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let DATA = try! JSONDecoder().decode(Root.self, from: data!)
print(DATA.response.charge_state.charge_port_door_open)
}
task.resume()
}
Since dataTask is asynchronous, you have to modify your method to return a value in a closure. A simple version without any error handling can look like this:
public func requestVehicleData(completion: #escaping ((Bool?) -> Void) {
let url = URL(string: "https://owner-api.teslamotors.com/api/1/vehicles/:id/vehicle_data")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue( "Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let responseData = data, let response = try? JSONDecoder().decode(Root.self, from: responseData) {
completion(response.response.charge_state.charge_port_door_open)
} else {
completion(nil)
}
}
task.resume()
}
You can also play around with the Swift Concurrency and user new async/await API for the same purpose.
P.S. When you have a chance, take a look at some basics and API Design Guideline.
Two simple approaches (if you don't want the call-site to have to use a completion block - which is also a perfectly good solution described in Maksym's answer) are..
perform any actions you need to perform with the value inside of the closure
create an optional property with a didSet on whatever owns RequestVehicleData. That way you can set the value within the closure and trigger the desired actions. An example for this would be:
class VehicleDataManager {
private var root: Root? {
didSet {
doSomething(with: root)
}
}
public func RequestVehicleData() {
let url = URL(string: "https://owner-api.teslamotors.com/api/1/vehicles/:id/vehicle_data")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue( "Bearer \(token)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
root = try! JSONDecoder().decode(Root.self, from: data!)
print(DATA.response.charge_state.charge_port_door_open)
}
task.resume()
}
}
The value is fetched asynchronously, so it will only be safely accessible in places that are guaranteed to get run after it has been returned.
Side note: I know this might have just been for the sake of a "quick and dirty" test function, but I would highly recommend not getting in the habit of force-unwrapping values that very well could be nil

Swift HTTP POST request with Combine

In my app, I am using the Combine framework to make network requests and it works fine for GET requests. But I am running into this issue with POST requests.
The following code (without Combine) works fine:
let data = ["statusTime": DateFormatter.iso8601Full.string(from: Date())]
let requestBody = try? JSONSerialization.data(withJSONObject: data, options: [])
let baseURL = "my-api.amazonaws.com"
let endpoint = "/my/endpoint"
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = baseURL
urlComponents.path = endpoint
let url = urlComponents.url!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = requestBody
request.addValue(authorizationToken, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("error: \(error)")
return
}
print("Response: \((response as! HTTPURLResponse).statusCode)")
}
task.resume()
I get a 200 response code from the above code.
The same code with Combine:
let data = ["statusTime": DateFormatter.iso8601Full.string(from: Date())]
let requestBody = try? JSONSerialization.data(withJSONObject: data, options: [])
let baseURL = "my-api.amazonaws.com"
let endpoint = "/my/endpoint"
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = baseURL
urlComponents.path = endpoint
let url = urlComponents.url!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = requestBody
request.addValue(authorizationToken, forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let cancellable = URLSession.shared
.dataTaskPublisher(for: request)
.print()
.sink(receiveCompletion: {
if let error = $0.error {
print("Failure: \(error)")
}
}, receiveValue: {
print("\($0)")
})
Here is the output I receive:
receive subscription: (DataTaskPublisher)
request unlimited
receive cancel
Seems like the request with the Combine framework is somehow getting cancelled? I used a network tracing app and I can see the first request (without Combine), but not the second request.
I have looked at numerous posts and documentation, but can't see what's wrong with my code. What am I missing? Thanks in advance.
I see you are creating the cancellable, but are you keeping a strong reference to it? It looks like you are creating the request, and then the cancellable goes out of scope and is deallocated, which cancels the request.
Declare something like this in your class:
var cancellables = Set<AnyCancellable>()
and then you implicitly store the cancellable as:
URLSession.shared // You don't hold on to the returned cancellable here
.dataTaskPublisher(for: request)
.print()
.sink(receiveCompletion: {
if let error = $0.error {
print("Failure: \(error)")
}
}, receiveValue: {
print("\($0)")
})
.store(in: &cancellables) // This puts the cancellable into the variables so it stays in scope

Swift used Codable, but the type is not correct

I know that Codable = Decodable & Encodable but when calling json from xcode,
Codable was given as a struct, but an error saying
Argument type'login.Type' does not conform to expected type'Encodable' appears.
json code
struct login: Codable {
var userId: String?
var userPw: String?
class func LoginBoard(_ completeHandler: #escaping (login) -> Void) {
let loginboard: String = MAIN_URL + "/member/login"
guard let url = URL(string: loginboard) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.httpBody = try? JSONEncoder().encode(login) // ERROR [Argument type 'login.Type' does not conform to expected type 'Encodable']
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
print("error calling Post on /todos/1")
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
let decoder = JSONDecoder.init()
let LoginList = try decoder.decode(login.self, from: responseData)
completeHandler(LoginList)
}
catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
There is no error in try decoder.decode
but only in urlRequest.httpBody = try? JSONEncoder().encode(login) what is the problem?
You need to have something like this to set the values.
let loginvalues = login(userId: "john", userPw: "adfadfa")
urlRequest.httpBody = try? JSONEncoder().encode(loginvalues)
If you place this inside a play ground and run it you will see that you get the json data.
struct Login: Codable {
var userId: String?
var userPw: String?
}
let loginvalues = Login(userId: "john", userPw: "adfadfa")
let test = try? JSONEncoder().encode(loginvalues)
print(String(data: test!, encoding: .utf8)!)

Get value from callback function swift

Question I want to get the value returned from my ApiToken function so I can use it in another function. For some reason I can not get the value from this function it will not return anything. How could I return the value from my ApiToken function and use it in another function.
Here is my GetApiToken class with the ApiToken function
class GetApiToken {
public func ApiToken(link: String, completionBlock: #escaping (String) -> Void) -> Void
{
let url = URL(string: link)!
let jsonDict = ["username": "snow", "password": "ssssssssss"]
let jsonData = try! JSONSerialization.data(withJSONObject: jsonDict, options: [])
var request = URLRequest(url: url)
request.httpMethod = "post"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("error:", error)
return
}
do {
guard let data = data else { return }
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: AnyObject] else { return }
//self.token = json["access_token"] as? String ?? "x"
completionBlock((json["access_token"] as? String)!)
} catch {
print("error:", error)
}
}
task.resume()
}
}
Here is where I am trying to get the value
func getData(_ link:String)
{
let url = URL(string: link)!
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: 20)
request.httpMethod = "GET"
var output = ""
GetApiToken().ApiToken(link: "http://localhost:5000/auth", completionBlock: { str in
output = str
})
request.addValue("JWT \(output)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type") ..........
It's an asynchronous call, so you need to put everything that will happen once the data has been retrieved in the completion callback
func getData(_ link:String)
{
let url = URL(string: link)!
var request = URLRequest(url: url,
cachePolicy: .reloadIgnoringCacheData,
timeoutInterval: 20)
request.httpMethod = "GET"
GetApiToken().ApiToken(link: "http://localhost:5000/auth",
completionBlock:
{ output in
request.addValue("JWT \(output)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
.......
})

Custom headers - Alamofire + Swift 3

I'm getting an error while trying to use this code:
func getRawJSON(method: String, paramether: String) {
let publicKey = "publicKeyHere"
let secretKey = "secretKeyHere
let APIURL = "https://www.bitmarket.pl/api2/"
let time = String(Int(NSDate().timeIntervalSince1970))
let query = NSURLComponents()
query.queryItems = [NSURLQueryItem(name: "method", value: method) as URLQueryItem,
NSURLQueryItem(name: "tonce", value: time) as URLQueryItem]
let requestString = query.query!
let requestData = Array(requestString.utf8)
let params = [
"method": method,
"tonce:": time
]
let hmac: Array<UInt8> = try! HMAC(key: secretKey.utf8.map({$0}), variant: .sha512).authenticate(requestData)
let hmacString = hmac.map{String(format: "%02X", $0)}.joined(separator: "").lowercased()
let URL = NSURL(string: APIURL)!
let mutableURLRequest = NSMutableURLRequest(url: URL as URL)
mutableURLRequest.httpMethod = "POST"
do {
mutableURLRequest.httpBody = try JSONSerialization.data(withJSONObject: paramether, options: JSONSerialization.WritingOptions())
} catch {
}
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
mutableURLRequest.setValue(publicKey, forHTTPHeaderField: "API-Key")
mutableURLRequest.setValue(hmacString, forHTTPHeaderField: "API-Hash")
Alamofire.request(mutableURLRequest) //Here is a problem
}
Here is the error:
Argument type 'NSMutableURLRequest' does not conform to expected type 'URLRequestConvertible'
What am I doing wrong? Alamofire documentation says NSMutableURLRequest could conform to URLRequestConvertible.
Swift 3 defines URLRequest which conforms to the protocol URLRequestConvertible. You should use URLRequest instead of NSMutableURLRequest.
Refer to this discussion.