1

Here I have 3 files loginView(SwiftUI file) for UI purpose, LoginViewModel for handling the logic, ServiceManager for handling the Network call

Below code is in loginView(SwiftUI file)

Button("Login") {
   loginVM.loginWebserviceCall()
}

Below code is in loginVM class

protocol LoginViewModelService: AnyObject {
    func getLoginWebServiceCall(url: URL, params: [String: Any], completion: @escaping (Result<LoginRequest, APIError>) -> ())
}


class LoginViewModel: ObservableObject {
    private weak var movieService: LoginViewModelService?
    @Published var error: NSError?

    init(movieService: LoginViewModelService = LoginStore.shared) {
       self.movieService = movieService
    }

    fileprivate func loginWebserviceCall() {
        let loginParams = ["username": "userEnteredUserName", "password": "userEnteredPassword", "request_token": "token"]
        self.movieService!.getLoginWebServiceCall(url: "API_URL",
                                             params: loginParams) { [weak self] (result) in
           guard let self = self else { return }
              switch result {
                case .success(let response):
                   print(response)
                case .failure(let error):
                   self.error = error as NSError
              }
       }
   }
}

class LoginStore: LoginViewModelService {
    static let shared = LoginStore()
    private init() {}

    func getLoginWebServiceCall(url: URL, params: [String: Any], completion: @escaping (Result<LoginRequest, APIError>) -> ()) {
        ServiceManager.shared().requestWebServiceCall(requestType: .POST, url: url, params: params, completion: completion)
    }
}

Below code is in ServiceManager class

class ServiceManager: NSObject {
    private static var manager: ServiceManager? = nil

    static func shared() -> ServiceManager {
        if manager == nil {
            manager = ServiceManager()
        }
        return manager!
    }

    func requestWebServiceCall<Response: Decodable>(requestType: HTTPMethod,
                                                    url: URL, params: [String: Any]? = nil,
                                             completion: @escaping (Result<Response, APIError>) -> ()) {            
        var urlRequest = URLRequest.init(url: url)
        if let params = params {
            let postData = try? JSONSerialization.data(withJSONObject: params, options: .init(rawValue: 0))
            urlRequest.httpBody = postData
        }

        urlRequest.httpMethod = requestType.rawValue
        urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

        URLSession.shared.dataTask(with: urlRequest) { [weak self] (data, response, error) in
            guard let self = self else { return }
            
            guard let data = data else {
                self.executeCompletionHandlerInMainThread(with: .failure(.noData), completion: completion)
                return
            }
            
            do {
               if let str = String(data: data, encoding: .utf8) {
                 print(str)
// Output: {"success":true,"expires_at":"2022-06-23 08:56:52 UTC","request_token":"6587563498567begjhgf3r5387853"}
                }

                let decodedResponse = try JSONDecoder().decode(Response.self, from: data)
                self.executeCompletionHandlerInMainThread(with: .success(decodedResponse), completion: completion)
            } catch let DecodingError.keyNotFound(key, context) {
                print("Key '\(key)' not found:", context.debugDescription)
                print("codingPath:", context.codingPath)
            } catch {
                print(error)
            }
        }.resume()
    }

    private func executeCompletionHandlerInMainThread<Response: Decodable>(with result: Result<Response, APIError>,
                                                                    completion: @escaping (Result<Response, APIError>) -> ()) {
        DispatchQueue.main.async {
            completion(result)
        }
    }
}

Below is the JSON we are expecting as response

{
  "success": true,
  "expires_at": "2018-07-24 04:10:26 UTC",
  "request_token": "1531f1a558c8357ce8990cf887ff196e8f5402ec"
}

But once I get response, the decoding is getting failed and it is going inside catch block(in ServiceManager class) and it print's below error.

Key 'CodingKeys(stringValue: "username", intValue: nil)' not found: No value associated with key CodingKeys(stringValue: "username", intValue: nil) ("username").
codingPath: []

It is showing username as not found. But in my API response, I don't have username at all.

But I am passing username as httpBody to this API.

What could be the reason? Why it is throwing error?

Bhanuteja
  • 771
  • 2
  • 8
  • 18
  • 1
    remove the `username` variable from `Response` – lorem ipsum Jun 22 '22 at 21:40
  • in `requestWebServiceCall`, could you add `print(String(data: data, encoding: .utf8))` just after `guard let data = data else {...}` to see what you **really** get from the server. – workingdog support Ukraine Jun 22 '22 at 21:40
  • 1
    It would also be helpful to see what your `Response` struct looks like. – burnsi Jun 22 '22 at 21:59
  • 1
    Your `Response` has a `username` field. Your JSON doesn't. Your `Response` needs to match your JSON. – jnpdx Jun 22 '22 at 22:10
  • @workingdogsupportUkraine I have added the code as you said and updated the 'requestWebServiceCall'. Also added the output what really I am getting – Bhanuteja Jun 23 '22 at 08:05
  • @loremipsum ' Response ' is a Decodable object, it has declared at ' requestWebServiceCall ' method in Generic way in Swift – Bhanuteja Jun 23 '22 at 08:09
  • @burnsi ' Response ' is not struct. It is a Decodable object, it has declared at ' requestWebServiceCall ' method in Generic way in Swift – Bhanuteja Jun 23 '22 at 08:10
  • @jnpdx ' Response ' is not struct/class as you think. It is a Decodable object, it has declared at ' requestWebServiceCall ' method in Generic way in Swift – Bhanuteja Jun 23 '22 at 08:11
  • 1
    @Bhanuteja you are right. I didn´t see that this is a generic function. Still, what does the type you decode to look like? Here `LoginRequest` type. As the error indicates there is a mismatch between `LoginRequest` and your `JSON` response. – burnsi Jun 23 '22 at 08:14
  • In your keys you have a username that has to be removed/optional/decoded only if present. Whatever response/login request looks like is irrelevant the error is telling you that you are trying to decode a username and it isn’t there. – lorem ipsum Jun 23 '22 at 08:30
  • @loremipsum Exactly 'username' is not available in the response. But it is throwing error as "No value associated". Something weird – Bhanuteja Jun 23 '22 at 14:03
  • 1
    Nothing weird, you are telling it to decode a username and it is not there. This is expected behavior – lorem ipsum Jun 23 '22 at 14:21
  • 2
    take @burnsi advice and show us the code for your `LoginRequest`. – workingdog support Ukraine Jun 23 '22 at 23:05

0 Answers0