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.
Related
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)
}
I am using the "JWT Authentication for WP REST API" plug-in to login in my wordpress in iOS.
When the access credentials are correct I get the response from the server:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbG...",
"user_email": "test#myhost.com",
"user_id": 1
}
If the data is incorrect, I get the answer from the server:
{
"code": "[jwt_auth] incorrect_password",
"message": "<strong>ERROR</strong>: Incorrect password",
"data": {
"status": 403
}
}
As I do to check, for example when the data is correct there is no 'code' and if the data is correct there is no 'token'
I tried like, but it does not work
let jsonObject = try! JSONSerialization.jsonObject(with: data!, options: JSONSerialization.ReadingOptions.mutableContainers) as! NSDictionary
if jsonObject["token"] == nil {
print("error password")
} else {
}
First of all use Decodable.
Create Response as enum with cases success and failure and associated types TokenData and ErrorData
enum Response : Decodable {
case success(TokenData)
case failure(ErrorData)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try .success(container.decode(TokenData.self))
} catch DecodingError.keyNotFound {
self = try .failure(container.decode(ErrorData.self))
}
}
}
struct TokenData : Decodable {
let token, userEmail : String
let userId : Int
}
struct ErrorData : Decodable {
let code, message : String
}
let jsonSuccessString = """
{
"token": "eyJ0eXAiOiJKV1QiLCJhbG...",
"user_email": "test#myhost.com",
"user_id": 1
}
"""
let jsonFailureString = """
{
"code": "[jwt_auth] incorrect_password",
"message": "<strong>ERROR</strong>: Incorrect password",
"data": {
"status": 403
}
}
"""
Decode the JSON and switch on the result, the example decodes both strings for demonstration
let successdata = Data(jsonSuccessString.utf8)
let failuredata = Data(jsonFailureString.utf8)
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let result1 = try decoder.decode(Response.self, from: successdata)
switch result1 {
case .success(let tokenData) : print(tokenData) // TokenData(token: "eyJ0eXAiOiJKV1QiLCJhbG...", userEmail: "test#myhost.com", userId: 1)
case .failure(let errorData) : print(errorData)
}
let result2 = try decoder.decode(Response.self, from: failuredata)
switch result2 {
case .success(let tokenData) : print(tokenData)
case .failure(let errorData) : print(errorData) // ErrorData(code: "[jwt_auth] incorrect_password", message: "<strong>ERROR</strong>: Incorrect password")
}
} catch {
print(error)
}
I use FCM to send push notification to my iOS app.When user click on the notification tray,the data handle by the function below:
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
print(userInfo['data'])
}
The userInfo is a [AnyHashable:Any] type.I successfully get the data from the userInfo['data'].So here is the data structure for userInfo['data'] :
'{"data":
{
"title":"My app",
"message":"The message here",
"payload":{
"post_id":"602"
},
"timestamp":"2018-03-10 14:12:08"
}
}'
Here is how I tried :
if let dataString = userInfo["data"] as? String {
let data = dataString.data(using: .utf8)!
do {
if let json = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? [String : Any]
{
let message = json["message"] as? String ?? "No message here"
let title = json["title"] as String ?? ""
//here is the problem..I have no idea to do it here
let payload = json["payload"] as? [String : Int] ?? [:]
for element in payload {
if let postId = element["post_id"] {
//print("postId = \(postId)")
}
}
} else {
print("bad json")
}
} catch let error as NSError {
print(error)
}
So as shown in above,I have no problem to get value of title,message and timestamp inside the data json.
But I have to idea how to get the value of post_id which is inside payload array.
So in this case,how to get the value of post_id from the data json above? Thanks.
Access post id like this
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let data = userInfo["data"] as? [String: Any],
let payload = data["payload"] as? [String: Any],
let postId = payload["post_id"] as? String{
print("post id \(postId)")
}else{
print("there is no post id inside the payload")
}
}
I want to filter my JSON and I can't find a way.
My JSON :
{
"id": "3",
"nom": "Blancs sablons",
"description": "Plage gigantesque, très souvent des surfeurs à l'eau."
},
{
"id": "4", // id to search
"nom": "Autre nom", // text to print
"description": "Encore une description"
},
{
"id": "5",
"nom": "Nom différent",
"description": "Et la dernière description"
},
I want to be able to print 'Autre nom' by calling
print(Spot[4].description)
Where 4 is the id
So I tried this struct Spot with constructor :
import Foundation
import MapKit
struct Spot : Decodable {
let nom : String
let description : String
let id: String
init(nom: String, description: String, id: String, img1: String, latitude: String, longitude: String) {
self.nom = nom
self.description = description
self.id = id
self.img1 = img1
self.latitude = latitude
self.longitude = longitude
}
}
And this to decode JSON :
func getSpots(){
guard let downloadURL = URL(string: "http://dronespot.fr/getSpot.php") else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("Oops Call for Help")
return
}
do {
let decoder = JSONDecoder()
let rates = try decoder.decode([Spot].self, from: data)
} catch {
print("Error after loading", error)
}
}.resume()
}
Any idea ?
You are getting an array of results back so you just need to select the one that you want out of the array.
In my test code I didn't find one with an id of 4.
You can filter the array using the filter high order function let rate = rates.filter { $0.id == "36" }
Here is the code I used in a Playground to test
//: Playground - noun: a place where people can play
import PlaygroundSupport
import UIKit
struct Spot : Decodable {
let nom : String
let description : String
let id: String
}
func getSpots(){
guard let downloadURL = URL(string: "http://dronespot.fr/getSpot.php") else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse, error in
guard let data = data, error == nil, urlResponse != nil else {
print("Oops Call for Help")
return
}
do {
let decoder = JSONDecoder()
let rates = try decoder.decode([Spot].self, from: data)
let rate = rates.filter { $0.id == "36" }
print(rate)
} catch {
print("Error after loading", error)
}
}.resume()
}
getSpots()
PlaygroundPage.current.needsIndefiniteExecution = true
[
-{
valid:"2",
invalid: "1",
pending: "2"
},
-{
valid:"0",
invalid: "1",
pending: "0"
},
-{
valid:"2",
invalid: "1",
pending: "2"
}
]
I am trying to parse this remote json and populate the data into an array.
I am struggling for hours trying to find out why my code isn't working,the array always ends up being empty. can somebody please tell me what am i doing wrong ?
var arrayreports : [Report] = []
var report = Report()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let host = appDelegate.host
if(Reachability.isConnectedToNetwork()){
let postEndpoint: String = host+"/api/reportbyworkflow/7"
let session = NSURLSession.sharedSession()
let url = NSURL(string: postEndpoint)!
session.dataTaskWithURL(url, completionHandler: { ( data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
guard let realResponse = response as? NSHTTPURLResponse where
realResponse.statusCode == 201 else {
print("Bad thing happened")
return
}
do {
if let ipString = NSString(data:data!, encoding: NSUTF8StringEncoding) {
let jsonDictionary:AnyObject! = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers)
let json = jsonDictionary as? Array<AnyObject>
for index in 0...json!.count-1 {
let contact : AnyObject? = json![index]
print(contact)
let collection = contact! as! Dictionary<String, AnyObject>
let valid = collection["valid"] as! String
let invalid = collection["invalid"] as! String
let pending = collection["pending"] as! String
report!.valid = Double(Int(valid)!)
report!.invalid = Double(Int(invalid)!)
report!.pending = Double(Int(pending)!)
arrayreports.append(report!)
}
}}
catch {
print("bad things happened")
}
}).resume()
}
If your json is really the one you copied here, it is not valid ( check on jsonvalidator.com ).
So it is normal than your serialization returns an empty array