Titus
Titus

Reputation: 349

How to fix invalid JSON response in swift when using get request in Alamofire?

I am creating an app wherein it pulls PatientList from API Server and it will display to a TableView. Upon checking, it returns 200 status code but falls to invalidJSON error. But when I checked in Postman, it returns 200 status code and pulls the records properly. I am quite confuse which part of my codes causes the error since I am new in swift. I am seeking help to solve the issue. Below are my sample codes for your references:

Patient.swift

struct Patient: Codable {
    let hospitalNumber: Int
    let patientName: String
    let totalAmount: Double

enum CodingKeys: String, CodingKey {
    case hospitalNumber = "hospitalNumber"
    case patientName = "patientName"
    case totalAmount = "totalAmount"
   }
}

APIService.swift

struct PatientList {
    typealias getPatientListTaskCompletion = (_ patientListperPayout: [Patient]?, _ error: NetworkError?) -> Void

    
    static func getPatientList(doctorNumber: Int, periodId: Int, completion: @escaping getPatientListTaskCompletion) {
        
        guard let patientPerPayoutURL = URL(string: "\(Endpoint.Patient.patientPerPayout)?periodId=\(periodId)&doctorNumber=\(doctorNumber)") else {
            completion(nil, .invalidURL)
            return
        }
        
        let sessionManager = Alamofire.SessionManager.default
        sessionManager.session.getAllTasks { (tasks) in
            tasks.forEach({ $0.cancel() })
        }
        
        Alamofire.request(patientPerPayoutURL, method: .get, encoding: JSONEncoding.default).responseJSON { (response) in
            guard HelperMethods.reachability(responseResult: response.result) else {
                completion(nil, .noNetwork)
                return
            }
            
            guard let statusCode = response.response?.statusCode else {
                completion(nil, .noStatusCode)
                return
            }
            
            switch(statusCode) {
            case 200:
                guard let jsonData = response.data else{
                    completion(nil, .invalidJSON)
                    print(statusCode)
                    return
                }
                
                let decoder = JSONDecoder()
                
                do {
                    let patientListArray = try decoder.decode([Patient].self, from: jsonData)
                    let sortedPatientListArray = patientListArray.sorted(by: { $0.patientName < $1.patientName })
                    completion(sortedPatientListArray, nil)
                }catch{
                    completion(nil, .invalidJSON)
                    print(statusCode)
                }
            case 400:
                completion(nil, .badRequest)
            case 404:
                completion(nil, .noRecordFound)
            default:
                print("UNCAPTURED STATUS CODE FROM getPatientList\nSTATUS CODE: \(statusCode)")
                completion(nil, .uncapturedStatusCode)
                }
            }
        }
    

Controller.swift

var patientList: [Patient]! {
    didSet {
       performSegue(withIdentifier: patientListIdentifier, sender: self)
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    
    self.latestCreditedAmountTableView.dataSource = self
    self.latestCreditedAmountTableView.delegate = self
    
    configureTableViewCell()
    showTotalCreditedAmount()
     getDoctorPayoutSummary(doctorNumber: doctorNumber)
    
}

func getDoctorPayoutSummary(doctorNumber: Int) {
  self.payoutSummary = payoutSummaryDetails
        self.taxRateVatRateLabel.text = "\(self.payoutSummary.taxRate) / \(self.payoutSummary.vatRate)"
        self.getPatientList()
        self.latestCreditedAmountTableView.reloadData()
        return
}

 func getPatientList() {

    APIService.PatientList.getPatientList(doctorNumber: doctorNumber, periodId: currentRemittance.periodId) { (patientListArray, error) in
        guard let patientListPerPayout = patientListArray, error == nil else {
            if let networkError = error {
                switch networkError {
                case .noRecordFound:
                    let alertController = UIAlertController(title: "No Record Found", message: "You don't have current payment remittance", preferredStyle: .alert)
                    alertController.addAction(UIAlertAction(title: "OK", style: .default))
                    self.present(alertController, animated: true, completion: nil)
                case .noNetwork:
                    let alertController = UIAlertController(title: "No Network", message: "\(networkError.rawValue)", preferredStyle: .alert)
                    
                    alertController.addAction(UIAlertAction(title: "OK", style: .default))
                    self.present(alertController, animated: true, completion: nil)
                default:
                    let alertController = UIAlertController(title: "Error", message: "There is something went wrong. Please try again", preferredStyle: .alert)
                    alertController.addAction(UIAlertAction(title: "OK", style: .default))
                    self.present(alertController, animated: true, completion: nil)
                }
            }
            return
        }
        
        self.patientList = patientListPerPayout
       
        return
        
    }
}

JSON Response

[
   {
   "hospitalNumber": null,
   "patientName": null,
   "totalAmount": 31104
   },
   {
   "hospitalNumber": "",
   "patientName": "LastName, FirstName",
   "totalAmount": 3439.8
   }
]

Upvotes: 0

Views: 1326

Answers (2)

Hitesh Surani
Hitesh Surani

Reputation: 13577

Just make below changes in your model class. Define your model class variable as optional which is not mandatory from APIs.

struct Patient: Codable {
    var hospitalNumber: String?
    let patientName: String?
    let totalAmount: Double?

    enum CodingKeys: String, CodingKey {
        case hospitalNumber = "hospitalNumber"
        case patientName = "patientName"
        case totalAmount = "totalAmount"
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)

        if let hospitalNumb = try container.decode(Int?.self, forKey: .hospitalNumber) {
            hospitalNumber = String(hospitalNumb)
        } else {
            hospitalNumber = try container.decode(String.self, forKey: .hospitalNumber)
        }
        patientName = try container.decode(String.self, forKey: .patientName)
        totalAmount = try container.decode(Double.self, forKey: .totalAmount)
    }
}

Note:

Codable OR Decodable is not working if the type is different for the same key or you can say like that type is different then specified type.

Upvotes: 0

Chris Shaw
Chris Shaw

Reputation: 1610

Your JSON response shows that some of the fields can be null - hospitalNumber and patientName at least. Also hospitalNumber is a string in the JSON - thanks to @Don for pointing out. Your struct should also be able to cope with these being nullable by making the mapped fields nullable also. I.e.

struct Patient: Codable {
  let hospitalNumber: String?
  let patientName: String?
  let totalAmount: Double

  enum CodingKeys: String, CodingKey {
    case hospitalNumber = "hospitalNumber"
    case patientName = "patientName"
    case totalAmount = "totalAmount"
  }
}

You will need to do the same for totalAmount if that can ever be null also. Whether the API is correct to return null in any circumstance is of course another question - how a null hospital number or name is useful may need to be addressed.

Make sure you do not force-unwrap the fields when you use them.

Upvotes: 1

Related Questions