I'm new to swift and I tried to make a get request to a api, but couldn't come up with an working result yet. All examples I tried but not worked at all
I need to send a json body to https://pincood.com/pincood/public/api/user/details and in Authorization I passed Bearer token like this "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIsImlzcyI6Imh0dHBzOi8vcGluY29vZC5jb20vcGluY29vZC9wdWJsaWMvYXBpL3VzZXIvbG9naW4iLCJpYXQiOjE2Njc4MjMxNzAsImV4cCI6MTY2ODE4MzE3MCwibmJmIjoxNjY3ODIzMTcwLCJqdGkiOiJVemo4bFp3ek16Z2FIV25QIn0.oCAk6db9c2BAhEGgU2gziYm2RX3hLbAtPUc7KQzIYWs" with GET request.
And in the output the data will display like this:
The json body only contains of one value
{
"id": 2,
"referral_code": "pn7R7m",
"referance_referral_code": "",
"first_name": "Uzma",
"last_name": "ansari",
"payment_mode": "CASH",
"email": "",
"gender": "MALE",
"birth_date": "2022-06-23",
"mobile": "9326257573",
"country_code": "+91",
"picture": "https://pincood.com/pincood/public/storage/user/profile/9326257573.png",
"device_token": "dW_jfRo94fM:APA91bFluxLzYICoYw6MslhYWEzxET8NYKH27MzSmQNRT6fNLdo6eAIB6KBZv9IvkFrSHUA2GUD1RfNw1e2XVdIdSZjDf-627PRLopzOwInifGdWIA4k-nIwLDghycCAlhwW0KJy76Xe",
"device_id": "ceae4b934e63a578",
"device_type": "android",
"login_by": "manual",
"social_unique_id": null,
"latitude": null,
"longitude": null,
"stripe_cust_id": null,
"wallet_balance": 0,
"rating": "5.00",
"otp": 811078,
"updated_at": "2022-11-06 12:44:01",
"emergency_contact1": "9999999999",
"emergency_contact2": "",
"deleted_at": null,
"currency": "₹",
"sos": "911",
"rental_content": "Dummy Content",
"outstation_content": "Dummy Content"
}
And I tried in the implementation like this
My Model :
struct TokenResponse: Codable {
let id : Int
let referral_code: String
let referance_referral_code: String
let first_name: String
let last_name: String
let payment_mode: String
let email: String
let gender: String
let birth_date: String
let mobile: String
let country_code: String
let picture: String
let device_token: String
let device_id: String
let device_type: String
let login_by: String
let social_unique_id: String
let latitude: String
let longitude: String
let stripe_cust_id: String
let wallet_balance: Int
let rating: String
let otp: Int
let updated_at: String
let emergency_contact1: String
let emergency_contact2: String
let deleted_at: String
let currency: String
let sos: String
let rental_content: String
let outstation_content: String
enum CodingKeys: String, CodingKey {
case id
case referral_code
case referance_referral_code
case first_name
case last_name
case payment_mode
case email, gender
case birth_date
case mobile
case country_code
case picture
case device_token
case device_id
case device_type
case login_by
case social_unique_id
case latitude, longitude
case stripe_cust_id
case wallet_balance
case rating, otp
case updated_at
case emergency_contact1
case emergency_contact2
case deleted_at
case currency, sos
case rental_content
case outstation_content
}
}
My function from where I trid to call my api
func getRequest(){
let url = URL(string: "https://pincood.com/pincood/public/api/user/details/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIsImlzcyI6Imh0dHBzOi8vcGluY29vZC5jb20vcGluY29vZC9wdWJsaWMvYXBpL3VzZXIvbG9naW4iLCJpYXQiOjE2Njc4MjMxNzAsImV4cCI6MTY2ODE4MzE3MCwibmJmIjoxNjY3ODIzMTcwLCJqdGkiOiJVemo4bFp3ek16Z2FIV25QIn0.oCAk6db9c2BAhEGgU2gziYm2RX3hLbAtPUc7KQzIYWs")!
var request = URLRequest(url: url)
request.allHTTPHeaderFields = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-",
"AUthorization":"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjIsImlzcyI6Imh0dHBzOi8vcGluY29vZC5jb20vcGluY29vZC9wdWJsaWMvYXBpL3VzZXIvbG9naW4iLCJpYXQiOjE2Njc4MjMxNzAsImV4cCI6MTY2ODE4MzE3MCwibmJmIjoxNjY3ODIzMTcwLCJqdGkiOiJVemo4bFp3ek16Z2FIV25QIn0.oCAk6db9c2BAhEGgU2gziYm2RX3hLbAtPUc7KQzIYWs"
]
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else { return }
guard let data = data, let _ = response else { return }
// handle data
do{
//here dataResponse received from a network request
let decoder = JSONDecoder()
let codabledata = try decoder.decode(TokenResponse.self, from: data)
print(codabledata)
//Response result
// Completion(codabledata)
} catch let parsingError {
print("Error", parsingError)
}
}.resume()
}
getRequest()
try this example code with the "modified" url string, and the corresponding data model that needs to match the json data you get from the server:
EDIT-1: with a callback from the asynchronous function.
Use it like this:
getRequest() { results in
print(results)
}
func getRequest(callback: #escaping (TokenResponse?) -> Void) {
let theToken = "...."
if let url = URL(string: "https://pincood.com/pincood/public/api/user/details") {
var request = URLRequest(url: url)
request.allHTTPHeaderFields = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-",
"Authorization":"Bearer \(theToken)"
]
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else { return }
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // <-- here
let codabledata = try decoder.decode(TokenResponse.self, from: data)
callback(codabledata) // <-- here
} catch {
print(error)
callback(nil) // <-- here
}
}.resume()
}
}
and
struct TokenResponse: Codable {
let id: Int
let referralCode, referanceReferralCode, firstName, lastName: String
let paymentMode, email, gender, birthDate: String
let mobile, countryCode, picture, deviceToken: String
let deviceId, deviceType, loginBy: String // <--
let socialUniqueId, latitude, longitude, stripeCustId: String? // <--
let walletBalance: Int
let rating: String
let otp: Int
let updatedAt, emergencyContact1, emergencyContact2: String
let deletedAt: String?
let currency, sos, rentalContent, outstationContent: String
}
You will need to consult the API docs to determine which properties are optional. In that case add ? to them.
EDIT-2: passing the token to the function:
func getRequest(token: String, callback: #escaping (TokenResponse?) -> Void) {
if let url = URL(string: "https://pincood.com/pincood/public/api/user/details") {
var request = URLRequest(url: url)
request.allHTTPHeaderFields = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-",
"Authorization":"Bearer \(token)" // <-- here
]
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else { return }
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let codabledata = try decoder.decode(TokenResponse.self, from: data)
callback(codabledata) // <-- here
} catch {
print(error)
callback(nil) // <-- here
}
}.resume()
}
}
And use it like this:
getRequest(token: "your-token-here") { results in
print(results)
}
Related
I am running into an issue building the correct data model for the following JSON response.
{
"resources": [
{
"courseid": 4803,
"color": "Blue",
"teeboxtype": "Championship",
"slope": 121,
"rating": 71.4
},
{
"courseid": 4803,
"color": "White",
"teeboxtype": "Men's",
"slope": 120,
"rating": 69.6
},
{
"courseid": 4803,
"color": "Red",
"teeboxtype": "Women's",
"slope": 118,
"rating": 71.2
}
]
}
Here is the current model. No matter what I do I can't seem to get the model populated. Here is also my URL session retrieving the data. I am new to Swift and SwiftUI so please be gentle. I am getting data back however I am missing something.
import Foundation
struct RatingsResources: Codable {
let golfcourserating : [GolfCourseRating]?
}
struct GolfCourseRating: Codable {
let id: UUID = UUID()
let courseID: Int?
let teeColor: String?
let teeboxtype: String?
let teeslope: Double?
let teerating: Double?
enum CodingKeysRatings: String, CodingKey {
case courseID = "courseid"
case teeColor = "color"
case teeboxtype
case teeslope = "slope"
case teerating = "rating"
}
}
func getCoureRating(courseID: String?) {
let semaphore = DispatchSemaphore (value: 0)
print("GETTING COURSE TEE RATINGS..........")
let urlString: String = "https://api.golfbert.com/v1/courses/\(courseID ?? "4800")/teeboxes"
print ("API STRING: \(urlString) ")
let url = URLComponents(string: urlString)!
let request = URLRequest(url: url.url!).signed
let task = URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
if let response = try? JSONDecoder().decode([RatingsResources].self, from: data) {
DispatchQueue.main.async {
self.ratingresources = response
}
return
}
print("*******Data String***********")
print(String(data: data, encoding: .utf8)!)
print("***************************")
let ratingsData: RatingsResources = try! decoder.decode(RatingsResources.self, from: data)
print("Resources count \(ratingsData.golfcourserating?.count)")
semaphore.signal()
}
task.resume()
semaphore.wait()
} //: END OF GET COURSE SCORECARD
First of all, never use try? while decoding your JSON. This will hide all errors from you. Use try and an appropriate do/catch block. In the catch block at least print the error.
Looking at your model there seem to be three issues here.
You don´t have an array of RatingsResources in your array. It is just a single instance.
let response = try JSONDecoder().decode(RatingsResources.self, from: data)
RatingsResources is not implemented correct.
let golfcourserating : [GolfCourseRating]?
should be:
let resources: [GolfCourseRating]?
Your coding keys are implemented wrong instead of:
enum CodingKeysRatings: String, CodingKey {
it should read:
enum CodingKeys: String, CodingKey {
You should add enum CodingKey with resources at struct RatingsResources
And decode:
if let response = try? JSONDecoder().decode(RatingsResources.self, from: data) {
// Your response handler
}
Hi im new in swift and im kinda still learning, so i try to make login controller and parse a json data if it corrects it parse a json data with id and stuff and if login is failed than the json will show a kinda message. i already make a struct for all the value data that required but i got this error that said its nil.
so, this is the json if the login is success :
[
{
"id": 891,
"name": "User",
"email": "qdpim#immobisp.com",
"status": "1"
} ]
and this is the json if login is failed :
[
{
"message": "Login Failed..",
"status": "0"
} ]
so basicly it has a same url i guess? but i dont know im kinda stuck in here and i need help
struct login : Codable {
let id : Int
let name : String
let email : String
let status : String
let message : String
init(dictionary : [String : Any]) {
id = (dictionary ["id"] as? Int)!
name = (dictionary ["name"] as? String)!
email = (dictionary ["email"] as? String)!
status = (dictionary ["status"] as? String)!
message = (dictionary ["message"] as? String)!
}
enum CodingKeys : String, CodingKey {
case id = "id"
case name = "name"
case email = "email"
case status = "status"
case message = "message"
}
}
func Login() {
let Email = EmailField.text!
let Pass = PasswordField.text!
print(api)
guard let JsonUrl = URL(string: api) else {return}
URLSession.shared.dataTask(with: JsonUrl) { (data, response, error) in
guard let data = data else {return}
do{
let parsing = try JSONDecoder().decode([login].self, from: data)
print(parsing)
self.Loginnn = parsing
let stats = self.Loginnn.map { $0.status}
if stats.contains("1"){
print("Login Success")
DispatchQueue.main.async {
self.appDelegate.loginSeque()
}
}else if stats.contains("0") {
let action = UIAlertAction(title: "Got It", style: .default, handler: nil)
let alert = UIAlertController(title: "Wrong Email / Password", message: "Please Try Again ", preferredStyle: .alert)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
// so basicly i wanna run this alert action by search status if its contains "0"
}
}
}catch{
print(error)
}
}.resume()
}
so when i try to test to failed my login, i doesnt show the message in my json in my log, instead it show this error
"keyNotFound(CodingKeys(stringValue: "id", intValue: nil),
Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "Index
0", intValue: 0)], debugDescription: "No value associated with key
CodingKeys(stringValue: \"id\", intValue: nil) (\"id\").",
underlyingError: nil))"
i just wanna pop some message or alert if the login is failed because or wrong password or email.....so maybe can someone help me how to do it the best way?
You can declare Success and Failure response types as below,
struct LoginSuccess: Decodable {
var id: Int
var name: String
var email: String
var status: String
}
struct LoginFailure: Decodable {
var status: String
var message: String
}
and then use as,
guard let JsonUrl = URL(string: api) else { return }
URLSession.shared.dataTask(with: JsonUrl) { (data, response, error) in
guard let data = data else { return }
if let success = try? JSONDecoder().decode([LoginSuccess].self, from: data).first {
GlobalVariable.UserId = String(success.id)
DispatchQueue.main.async {
self.appDelegate.loginSeque()
}
} else if let failure = try? JSONDecoder().decode([LoginFailure].self, from: data).first {
let action = UIAlertAction(title: "Got It", style: .default, handler: nil)
let alert = UIAlertController(title: "Wrong Email / Password", message: failure.message, preferredStyle: .alert)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}.resume()
In this situation I would use JSONSerialization to decode the data to a [[String: Any]] and look at the content to determine what kind of message it is.
In my code I have assumed the "status" item tells us if it was a successful login or not but one could for instance look for the presence of "id" or the count of elements in the dictionary as well to determine the type of response
do {
let result = try JSONSerialization.jsonObject(with: data) as! [[String: Any]]
if let response = result.first, let status = response["status"] as? String {
if status == "1" {
if let id = response["id"] as? Int {
let ids = String(id)
//...
}
} else {
if let message = response["message"] as? String {
print(message)
}
}
}
} catch {
print(error)
}
Below is my solution used in the code from your question. Note that I have simplified the Login struct since it is only used when login was successful
struct Login {
let id : Int
let name : String
let email : String
}
do {
let result = try JSONSerialization.jsonObject(with: data) as! [[String: Any]]
if let response = result.first, let status = response["status"] as? String {
if status == "1" {
//handle success
let login = Login(id: response["id"] as? Int ?? 0,
name: response["name"] as? String ?? "",
email: response["email"] as? String ?? "")
self.Loginnn = login
DispatchQueue.main.async {
self.appDelegate.loginSeque()
}
} else {
let action = UIAlertAction(title: "Got It", style: .default, handler: nil)
let alert = UIAlertController(title: "Wrong Email / Password", message: "Please Try Again ", preferredStyle: .alert)
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
}
} catch {
print(error)
}
The success response only contains the keys ("id", "name", "email", "status")
[ { "id": 891, "name": "User", "email": "qdpim#immobisp.com", "status": "1" } ]
and the failure response only contains the keys ("message", "status")
[ { "message": "Login Failed..", "status": "0" } ]
If you want to use the same struct for both JSON responses, you should make the properties optional
struct login : Codable {
var id: Int?
var name: String?
var email: String?
var status: String?
var message: String?
}
Also, since your keys are the same as your properties, you don't need enum CodingKeys or init for that matter if you use JSONDecoder().decode
You've already got an answer (or three) for this, but I want to show you how to do it without using JSONSerialization or speculative decoding.
So we have some LoginSuccess and LoginFailure types that you want to decode:
struct LoginSuccess: Decodable {
var id: Int
var name: String
var email: String
}
struct LoginFailure: Decodable {
var message: String
}
And we want to discriminate between them based on a status that is in the same container as the fields of those types. So we create an enum:
enum LoginResult: Decodable {
case success(LoginSuccess)
case failure(LoginFailure)
enum Keys: CodingKey {
case status
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: Keys.self)
if try container.decode(String.self, forKey: .status) == "1" {
self = .success(try LoginSuccess(from: decoder))
} else {
self = .failure(try LoginFailure(from: decoder))
}
}
}
Note that the enum's init does not call decoder.decode(LoginSuccess.self). It passes the decoder it was given to the LoginSuccess initializer. Same with LoginFailure. This means those initializers will extract values from the same container as the status field.
Test:
let successData = #"[ { "id": 891, "name": "User", "email": "qdpim#immobisp.com", "status": "1" } ]"#.data(using: .utf8)!
print(try JSONDecoder().decode([LoginResult].self, from: successData))
// Output:
[__lldb_expr_1.LoginResult.success(__lldb_expr_1.LoginSuccess(id: 891, name: "User", email: "qdpim#immobisp.com"))]
let failureData = #"[ { "message": "Login Failed..", "status": "0" } ]"#.data(using: .utf8)!
print(try JSONDecoder().decode([LoginResult].self, from: failureData))
// Output:
[__lldb_expr_1.LoginResult.failure(__lldb_expr_1.LoginFailure(message: "Login Failed.."))]
Note that because your example data is wrapped in [...], I decoded arrays of LoginResult.
This is the code I am using but I am unable to fetch the JSON.
Error message:
Expected to decode Dictionary<String, Any> but found an array instead.
func getMovieByName(completion: #escaping (Bool, Any?, Error?) -> Void) {
guard let url = URL(string:"https://api.themoviedb.org/3/search/movie?api_key=4cb1eeab94f45affe2536f2c684a5c9e&query='star") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
let items = try JSONDecoder().decode(ItemList.self, from: data)
DispatchQueue.main.async {
completion(true, items, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
task.resume()
}
JSON:
{
"page": 1,
"total_results": 2102,
"total_pages": 106,
"results": [{
"vote_count": 9052,
"id": 11,
"video": false,
"vote_average": 8.2,
"title": "Star Wars",
"popularity": 31.502792,
"poster_path": "/btTdmkgIvOi0FFip1sPuZI2oQG6.jpg",
"original_language": "en",
"original_title": "Star Wars",
"genre_ids": [
12,
28,
878
],
"backdrop_path": "/4iJfYYoQzZcONB9hNzg0J0wWyPH.jpg",
"adult": false,
"overview": "Princess Leia is captured and held hostage by the evil Imperial forces in their effort to take over the galactic Empire. Venturesome Luke Skywalker and dashing captain Han Solo team together with the loveable robot duo R2-D2 and C-3PO to rescue the beautiful princess and restore peace and justice in the Empire.",
"release_date": "1977-05-25"
}]
}
struct Results: Codable {
let id: Int
let title: String
let poster_path: String
struct ItemList: Codable {
let results: Results
}
}
You can create a Swift Struct for this purpose. Here is how you do it.
import Foundation
struct MovieStruct: Codable {
let page, totalResults, totalPages: Int?
let results: [Result]?
enum CodingKeys: String, CodingKey {
case page
case totalResults = "total_results"
case totalPages = "total_pages"
case results
}
}
struct Result: Codable {
let voteCount, id: Int?
let video: Bool?
let voteAverage: Double?
let title: String?
let popularity: Double?
let posterPath, originalLanguage, originalTitle: String?
let genreIDS: [Int]?
let backdropPath: String?
let adult: Bool?
let overview, releaseDate: String?
enum CodingKeys: String, CodingKey {
case voteCount = "vote_count"
case id, video
case voteAverage = "vote_average"
case title, popularity
case posterPath = "poster_path"
case originalLanguage = "original_language"
case originalTitle = "original_title"
case genreIDS = "genre_ids"
case backdropPath = "backdrop_path"
case adult, overview
case releaseDate = "release_date"
}
}
After you have created the struct, you can do something like this to parse your data.
func getMovieByName(completion: #escaping (Bool, Any?, Error?) -> Void) {
guard let url = URL(string:"https://api.themoviedb.org/3/search/movie?api_key=4cb1eeab94f45affe2536f2c684a5c9e&query='star") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
let items = try? JSONDecoder().decode(MovieStruct.self, from: jsonData)
DispatchQueue.main.async {
completion(true, items, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
task.resume()
}
Note I have created the struct with the JSON which you have given. I hope it helps.
func getMovieByName(completion: #escaping (Bool, Any?, Error?) -> Void) {
guard let url = URL(string:"https://api.themoviedb.org/3/search/movie?api_key=4cb1eeab94f45affe2536f2c684a5c9e&query='star") else { return }
let session = URLSession.shared
let task = session.dataTask(with: url) { (data, _, _) in
guard let data = data else { return }
do {
let items = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
completion(true, items, nil)
}
} catch {
DispatchQueue.main.async {
completion(false, nil, error)
}
}
}
task.resume()
}
Result struct contains the contents of "results"
struct Result: Codable {
var results: [Movie]
}
Add variables to correspond to the item's fields
struct Movie: Codable {
var id: Int
var vote_count: Int
var title: String
//etc..
}
I am confusing to getting detail of fruit
{
"fruits": [
{
"id": "1",
"image": "https://cdn1.medicalnewstoday.com/content/images/headlines/271/271157/bananas.jpg",
"name": "Banana"
},
{
"id": "2",
"image": "http://soappotions.com/wp-content/uploads/2017/10/orange.jpg",
"title": "Orange"
}
]
}
Want to parse JSON using "Decodable"
struct Fruits: Decodable {
let Fruits: [fruit]
}
struct fruit: Decodable {
let id: Int?
let image: String?
let name: String?
}
let url = URL(string: "https://www.JSONData.com/fruits")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do{
let fruits = try JSONDecoder().decode(Fruits.self, from: data)
print(Fruits)
}catch {
print("Parse Error")
}
also can you please suggest me cocoapod library for fastly download images
The issue you are facing is because your JSON is returning different data for your Fruits.
For the 1st ID it returns a String called name, but in the 2nd it returns a String called title.
In addition when parsing the JSON the ID appears to be a String and not an Int.
Thus you have two optional values from your data.
As such your Decodable Structure should look something like this:
struct Response: Decodable {
let fruits: [Fruits]
}
struct Fruits: Decodable {
let id: String
let image: String
let name: String?
let title: String?
}
Since your URL doesn't seem to be valid, I created the JSON file in my main bundle and was able to parse it correctly like so:
/// Parses The JSON
func parseJSON(){
if let path = Bundle.main.path(forResource: "fruits", ofType: "json") {
do {
let data = try Data(contentsOf: URL(fileURLWithPath: path), options: .mappedIfSafe)
let jsonResult = try JSONDecoder().decode(Response.self, from: data)
let fruitsArray = jsonResult.fruits
for fruit in fruitsArray{
print("""
ID = \(fruit.id)
Image = \(fruit.image)
""")
if let validName = fruit.name{
print("Name = \(validName)")
}
if let validTitle = fruit.title{
print("Title = \(validTitle)")
}
}
} catch {
print(error)
}
}
}
Hope it helps...
// Parse Json using decodable
// First in create Structure depends on json
//
//
//
struct Countory : Decodable {
let name: String
let capital: String
let region: String
}
let url = "https://restcountries.eu/rest/v2/all"
let urlObj = URL(string: url)!
URLSession.shared.dataTask(with: urlObj) {(data, responds, Error) in
do {
var countories = try JSONDecoder().decode([Countory].self, from: data!)
for country in countories {
print("Country",country.name)
print("###################")
print("Capital",country.capital)
}
} catch {
print(" not ")
}
}.resume()
Model sample:
public struct JsonData: Codable{
let data: [Data]?
let meta: MetaValue?
let linksData: LinksValue?
private enum CodingKeys: String, CodingKey{
case data
case meta
case linksData = "links"
}
}
enum BackendError: Error {
case urlError(reason: String)
case objectSerialization(reason: String)
}
struct APIServiceRequest {
static func serviceRequest<T>(reqURLString: String,
resultStruct: T.Type,
completionHandler:#escaping ((Any?, Error?) -> ())) where T : Decodable {
guard let url = URL(string: reqURLString) else {
print("Error: cannot create URL")
let error = BackendError.urlError(reason: "Could not construct URL")
completionHandler(nil, error)
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
completionHandler(nil, error)
return
}
guard let responseData = data else {
print("Error: did not receive data")
let error = BackendError.objectSerialization(reason: "No data in response")
completionHandler(nil, error)
return
}
let decoder = JSONDecoder()
do {
let books = try decoder.decode(resultStruct, from: responseData)
completionHandler(books, nil)
} catch {
print("error trying to convert data to JSON")
print(error)
completionHandler(nil, error)
}
}
task.resume()
}
}
To Access:
let apiService = APIServiceRequest()
var dataArray: [String: Any]? //global var
apiService.serviceRequest(reqURLString: endPoint, resultStruct: VariantsModel.self, completionHandler: {dataArray,Error in})
POST Method
func loginWS(endpoint: String, completionHandler: #escaping (Any?) -> Swift.Void) {
guard let sourceUrl = URL(string: endpoint) else { return }
let request = NSMutableURLRequest(url: sourceUrl)
let session = URLSession.shared
request.httpMethod = "POST"
request.addValue(vehiceHeader, forHTTPHeaderField: "X-Vehicle-Type")
request.addValue(contentHeader, forHTTPHeaderField: "Content-Type")
let task = session.dataTask(with: request as URLRequest) { data, response, error in
guard let data = data else { return }
do {
let responseData = try JSONDecoder().decode(JsonData.self, from: data)
print("response data:", responseData)
completionHandler(responseData)
} catch let err {
print("Err", err)
}
}.resume()
}
I have an API for my user to login to the database . I am using the following code to send the credentials to the API and if they are valid I am going to get some JSON typed data about the users information . Otherwise I get a string saying that the username or password is wrong .
Here is my HTTTP Post request :
let url = URL(string: "http://128.199.199.17:3000/api/login")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let postString = "email=22222#gmail.com&password=123456"
request.httpBody = postString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("\(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString!)")
}
task.resume()
It works fine and I get the following information in the console :
{
"user_id": 2,
"email": "22222#gmail.com",
"password": "123456",
"user_name": "number 2 name",
"full_name": "danial kosarifar",
"sex": "male",
"height": 0,
"weight": 0,
"number_of_meal_per_day": 3,
"water_amount": 0,
"calories": 0,
"number_of_hours_to_sleep_per_day": 3,
"createdAt": "2017-11-14T17:23:31.000Z",
"updatedAt": "2017-11-14T17:25:37.000Z"
}
I've also created a decodable structure like so :
struct User : Decodable {
let user_id : Int
let email : String
let password : String
let username : String
}
my question is instead of decoding the data to string how can I decode them in such way that I can put them in the structure thats I've defined . I am completely new to this topic please bear with me if my question too of much of a beginner .
Thanks
Swift 4:
It's better for creating struct as Codable like below,
struct User:Codable { //Because enum CodingKeys: String, CodingKey {
let userID : Int
let email : String
let password : String
let userName : String
let fullName:String
let sex:String
let height:Int
let weight:Int
let numberOfMealPerDay:Int
let waterAmount:Int
let calories:Int
let numberOfHoursToSleppPerDay:Int
let createdAt:String
let updatedAt:String
enum CodingKeys: String, CodingKey {
case userID = "user_id"
case email
case password
case userName = "user_name"
case fullName = "full_name"
case sex
case height
case weight
case numberOfMealPerDay = "number_of_meal_per_day"
case waterAmount = "water_amount"
case calories
case numberOfHoursToSleppPerDay = "number_of_hours_to_sleep_per_day"
case createdAt
case updatedAt
}
}
By using JSONDecoder:
Then response data data inside the closure task can be parsed by using JSONDecoder. Here, user is the class variable. i.e.,var user:User?
if let json = try? JSONDecoder().decode(User.self, from: data!){
self.user = json
}
Note: For better understanding, this is my video series about JSON parsing in swift 4
You can add a custom initializer to your user struct that takes the json data as a parameter and make it throws. You will need also to create a custom date formatter to parse your dates (you need to include the milliseconds to it). Note that if your json response may not include some keys/values you will need to make that property optional:
So for the date formatter you can use this custom date formatter from this answer:
extension Formatter {
static let iso8601: DateFormatter = {
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"
return formatter
}()
}
Your user struct should look like this:
struct User: Codable {
let id: Int
let email: String
let password: String
let userName: String
let fullName: String
let sex: String
let height: Int
let weight: Int
let mealsPerDay: Int
let waterAmount: Int
let calories: Int
let hoursToSleepPerDay: Int
let createdAt: Date
let updatedAt: Date
// you can provide different keys to your user struct properties
private enum CodingKeys: String, CodingKey {
case id = "user_id", email, password, userName = "user_name", fullName = "full_name", sex, height, weight, mealsPerDay = "number_of_meal_per_day", waterAmount = "water_amount", calories, hoursToSleepPerDay = "number_of_hours_to_sleep_per_day", createdAt, updatedAt
}
// custom initializer that takes the json data and throws in case of error
init(data: Data) throws {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(Formatter.iso8601)
self = try decoder.decode(User.self, from: data)
}
}
usage:
let data = Data("""
{
"user_id": 2,
"email": "22222#gmail.com",
"password": "123456",
"user_name": "number 2 name",
"full_name": "danial kosarifar",
"sex": "male",
"height": 0,
"weight": 0,
"number_of_meal_per_day": 3,
"water_amount": 0,
"calories": 0,
"number_of_hours_to_sleep_per_day": 3,
"createdAt": "2017-11-14T17:23:31.000Z",
"updatedAt": "2017-11-14T17:25:37.000Z"
}
""".utf8)
do {
let user = try User(data: data)
print(user) // User(id: 2, email: "22222#gmail.com", password: "123456", userName: "number 2 name", fullName: "danial kosarifar", sex: "male", height: 0, weight: 0, mealsPerDay: 3, waterAmount: 0, calories: 0, hoursToSleepPerDay: 3, createdAt: 2017-11-14 17:23:31 +0000, updatedAt: 2017-11-14 17:25:37 +0000)\n"
} catch {
print(error)
}