2

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": "[email protected]", "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?

1
  • Yes my json id is : Int not string Commented Jun 19, 2019 at 5:00

4 Answers 4

1

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()
Sign up to request clarification or add additional context in comments.

4 Comments

it still has this ( "keyNotFound(CodingKeys(stringValue: "id", intValue: nil)" ) sir
What is the crash? What line it crashes?
It works sir thank you very much, it just the crash from that alert action....i still figure it out but any help?
@afipermana You can add that error inside your question or you can ask another question if the issue in this question is fixed.
0

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

Comments

0

The success response only contains the keys ("id", "name", "email", "status")

[ { "id": 891, "name": "User", "email": "[email protected]", "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

2 Comments

thanks but i already try that, i doesnt work because i wanna make that id to become global var with this line of code : let ids = self.Loginnn.map {$0.id} if let id = ids.first{ GlobalVariable.UserId = String(id) } and i got an error that ("Cannot invoke initializer for type 'String' with an argument list of type '(Int?)' ") and when i try to use "!" in my : (" GlobalVariable.UserId = String(id!) ") it crash everytime i try to failed my login process
@afipermana This seems to be a different problem to the one stated in your question. Your question was regarding the error and the reason for that error was that JSONDecoder expected all the values to be present in the JSON response and didn't know what to do when it couldn't find id in the error response. If you have a different question, please ask it as a new question
0

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": "[email protected]", "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: "[email protected]"))]

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.

1 Comment

woww thanks for that....since im a newbie in Ios thing this one make me figure new way to decode....thanks alot!!!!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.