So I'm practising trying to make API calls with Swift. The code is as below:
struct Example: Codable {
let userID: String
let ID: String
let title: String
let completed: String
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
if let json: Example = try? decoder.decode(Example.self, from: data) {
completion(json)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.ID)
}
However, I am unable to print out anything when getJson is called.
The example API I used is found here.
The tutorial that I used to help me write the code is found here.
Modified your variable name and data type exactly as your API response.
struct Example: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
do {
let json: Example = try! decoder.decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.id)
}
You can also use CodingKey and can change your response during the init period.
struct Example: Codable {
var userID: Int
var ID: Int
var title: String
var completed: Bool
enum CodingKeys: String, CodingKey {
case userID = "userId"
case ID = "id"
case title = "title"
case completed = "completed"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
userID = try values.decode(Int.self, forKey: .userID)
ID = try values.decode(Int.self, forKey: .ID)
title = try values.decode(String.self, forKey: .title)
completed = try values.decode(Bool.self, forKey: .completed)
title = "Title: \(title)"
}
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
do {
let json: Example = try JSONDecoder().decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print("ID: \(json.ID) \(json.title)")
}
import UIKit
class ApiManager: NSObject {
static func postApiRequest(url: String,parameters:[String: Any],completion: #escaping([String: Any])-> Void) {
let session = URLSession.shared
let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions())
let task = session.dataTask(with: request as URLRequest as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
print ("data = \(jsonResponse)")
}catch _ {
print ("OOps not good JSON formatted response")
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
}
}
}
Related
I have to use an api link and the website said that I have to add my key in the header
but I don t know how to add it
I tried like this but is not working :(
can someone please help?
struct PollenRequest {
let resourceUrl: URL
let API_KEY = "123wrsgsdfhseraq24eewfesd"
init(location: String){
let resourceString = "https://api.ambeedata.com/latest/pollen/by-place?place=\(location)"
guard let resourceUrl = URL(string: resourceString) else { fatalError() }
let request = NSMutableURLRequest(url: NSURL(string: resourceString)! as URL)
request.addValue("123wrsgsdfhseraq24eewfesd",forHTTPHeaderField: "x-api-key")
self.resourceUrl = resourceUrl
}
func getPollen (completion: #escaping(Result<[PollenData], PollenError>) -> Void){
let dataTask = URLSession.shared.dataTask(with: resourceUrl){data, _, _ in
guard let jsonData = data else {
completion(.failure(.noData))
return
}
do{
let decoder = JSONDecoder()
let pollenResponse = try decoder.decode(PollenResponse.self, from: jsonData)
let pollenDetails = pollenResponse.data
completion(.success(pollenDetails))
}catch{
completion(.failure(.notProcessedData))
}
}
dataTask.resume()
}
}
Rather than saving the URL you have to save the URLRequest
And don't use NS.. types if there is a native Swift equivalent
struct PollenRequest {
let urlRequest: URLRequest
let API_KEY = "123wrsgsdfhseraq24eewfesd"
init(location: String) {
let resourceString = "https://api.ambeedata.com/latest/pollen/by-place?place=\(location)"
guard let resourceUrl = URL(string: resourceString) else { fatalError() }
var request = URLRequest(url: resourceUrl)
request.addValue(API_KEY, forHTTPHeaderField: "x-api-key")
request.addValue("application/json", forHTTPHeaderField: "Content-type")
self.urlRequest = request
}
func getPollen (completion: #escaping(Result<[PollenData], PollenError>) -> Void){
let dataTask = URLSession.shared.dataTask(with: urlRequest) {data, _, _ in
guard let jsonData = data else {
completion(.failure(.noData))
return
}
do{
let decoder = JSONDecoder()
let pollenResponse = try decoder.decode(PollenResponse.self, from: jsonData)
let pollenDetails = pollenResponse.data
completion(.success(pollenDetails))
}catch{
completion(.failure(.notProcessedData))
}
}
dataTask.resume()
}
}
Side note:
In case of an error returning .notProcessedData is meaningless. You should return the real DecodingError
I have the following Struct that I want to initialize, and then use its method query() to mutate its result property.
Query() sends and fetches JSON data, then decodes it to a String. When I declare query() as a mutating function, I receive the error "Escaping closure captures mutating 'self' parameter" in my URLSession.
What do I need to change?
The call:
var translation = Translate(string: "hello", base: "en", target: "de", result: "")
translation.query()
let translated = translation.result
The struct:
struct Translate {
let string: String, base: String, target: String
var result: String
mutating func query() {
let body: [String: String] = ["q": self.string, "source": self.base, "target": self.target]
let bodyData = try? JSONSerialization.data(withJSONObject: body)
guard let url = URL(string: "https://libretranslate.com/translate") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = bodyData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
DispatchQueue.main.async {
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
if responseJSON["translatedText"] != nil {
self.result = responseJSON["translatedText"] as! String
}
}
}
return
}
.resume()
}
}
Xcode error:
There are many issues in the code.
The most significant issue is that the URLRequest is asynchronous. Even if no error occurred result will be always empty.
You have to add a completion handler – it fixes the errors you got by the way – and it's highly recommended to handle all errors.
Instead of JSONSerialization the code uses JSONDe/Encoder
struct Translation : Decodable { let translatedText : String }
struct Translate {
let string: String, base: String, target: String
func query(completion: #escaping (Result<String,Error>) -> Void) {
let body: [String: String] = ["q": self.string, "source": self.base, "target": self.target]
do {
let bodyData = try JSONEncoder().encode(body)
let url = URL(string: "https://libretranslate.com/translate")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = bodyData
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(Translation.self, from: data!).translatedText} )
}
.resume()
} catch {
completion(.failure(error))
}
}
}
let translation = Translate(string: "hello", base: "en", target: "de")
translation.query() { result in
DispatchQueue.main.async {
switch result {
case .success(let translated): print(translated)
case .failure(let error): print(error)
}
}
}
Both exclamation marks (!) are safe.
I am following a tutorial on REST API calls with Swift and Codable. I cannot compile the following although I was careful when I typed all of it. Can anyone tell me what's wrong? Also, can anyone point me to a better tutorial? The error is:
Catch block is unreachable
and also
cannot find json in scope
import UIKit
import Foundation
struct Example: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
do {
let json: Example = try! decoder.decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.id)
}
}
struct Example: Decodable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
struct APIRequest {
var resourceURL: URL
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
init() {
resourceURL = URL(string: urlString)!
}
//create method to get decode the json
func requestAPIInfo(completion: #escaping(Result<Example, Error>) -> Void) {
let dataTask = URLSession.shared.dataTask(with: resourceURL) { (data, response, error) in
guard error == nil else {
print (error!.localizedDescription)
print ("stuck in data task")
return
}
let decoder = JSONDecoder()
do {
let jsonData = try decoder.decode(Example.self, from: data!)
completion(.success(jsonData))
}
catch {
print ("an error in catch")
print (error)
}
}
dataTask.resume()
}
}
class ViewController: UIViewController {
let apiRequest = APIRequest()
override func viewDidLoad() {
super.viewDidLoad()
apiRequest.requestAPIInfo { (apiResult) in
print (apiResult)
}
}
}
Instead of "do catch", you can use "guard let"
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
guard let data = data else {return print("error with data")}
let decoder = JSONDecoder()
guard let json: Example = try? decoder.decode(Example.self, from: data) else {return print("error with json")}
completion(json)
}.resume()
}
}
This does NOT handle errors, so my solution is just to an answer to your question, not a general solution for every similar cases.
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)!)
I need add a new function that returns songs from Apple Music API. The code below returns nil however, it works for Album ID. I modified the code from APIClient.swift. See source for details.
It returns song(id:completion:) JSON Decode Failed.
Source: https://github.com/shingohry/AppleMusicSearch
SongModel.swift
func song(id: String, completion: #escaping (Resource?) -> Swift.Void) {
let completionOnMain: (Resource?) -> Void = { resource in
DispatchQueue.main.async {
completion(resource)
}
}
guard let url = URL(string: "https://api.music.apple.com/v1/catalog/\(APIClient.countryCode)/songs/\(id)") else { return }
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("Bearer \(APIClient.developerToken)",
forHTTPHeaderField: "Authorization")
data(with: request) { data, error -> Void in
guard error == nil else {
print(#function, "URL Session Task Failed", error!)
completionOnMain(nil)
return
}
guard let jsonData = try? JSONSerialization.jsonObject(with: data!),
let dictionary = jsonData as? [String: Any],
let dataArray = dictionary["data"] as? [[String: Any]],
let songDictionary = dataArray.first,
let songData = try? JSONSerialization.data(withJSONObject: albumDictionary),
let album = try? JSONDecoder().decode(Resource.self, from: songData) else {
print(#function, "JSON Decode Failed");
completionOnMain(nil)
return
}
completionOnMain(song)
}
}