Search code examples
jsonswiftapicodableurlsession

Having trouble with my API Request. Curl Method with 2 headers


I would like some assistance trying to find out why I can't process this curl URLRequest in Swift. I believe my issue is how I am adding my headers but I can't figure this out. I've tried multiple ways without success. All the examples I am finding here don't seem to work for this situation.

When I get to the JSONDecoder I think it's decoding something, but not what I am expecting and the debugger doesn't get to the catch error.

Any assistance would be greatly appreciated!

import Foundation
import SwiftUI

class Network: ObservableObject {
    @Published var Loan: [LoanSelected] = []
            
    func getLoan() {
        guard let url = URL(string: "https://absws.com/TmoAPI/v1/LSS.svc/GetLoan/1001")
        else
        {
            return
        }
        
        var urlRequest = URLRequest(url: url)
        let headers = [
            "Token": "VIC",
            "Database": "World"
        ]
        
        //urlRequest.httpMethod = "GET"
        //urlRequest.addValue("ABS", forHTTPHeaderField: "Token")
        //urlRequest.addValue("World Mortgage Company", forHTTPHeaderField: "Database")
        //urlRequest.httpBody = payload
        urlRequest.httpMethod = "GET"
        urlRequest.allHTTPHeaderFields = headers

        
        let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
            if let error = error {
                print("Request error: ", error)
                return
            }
            
            guard let response = response as? HTTPURLResponse else { return }
            
            if response.statusCode == 200 {
                guard let data = data else { return }
                DispatchQueue.main.async {
                    do {
                        let decodedUsers = try JSONDecoder().decode([LoanSelected].self, from: data)
                        self.Loan = decodedUsers
                    } catch let error {
                        print("Error decoding: ", error)
                    }
                }
            }
        }
        
        dataTask.resume()
    }
}

Solution

  • You are not getting an array [LoanSelected] from the server with the url you show.

    Try this approach, using model struts that match the json data you are getting from the API.

    Here is my test code, works well for me.

    Use https://app.quicktype.io/ to generate the correct model structs for you.

    struct ContentView: View {
        @StateObject var network = Network()
        
        var body: some View {
            ScrollView {
                Text("Loan")
                    .font(.title)
                    .bold()
                
                VStack(alignment: .leading) {
                    HStack(alignment:.top) {
                        Text("\(network.loan?.account ?? "no account")")
                        VStack(alignment: .leading) {
                            Text(network.loan?.bankName ?? "no bank").bold()
                        }
                    }
                    .frame(width: 300, alignment: .leading)
                    .padding()
                    .cornerRadius(20)
                }
            }
            .padding(.vertical)
            .onAppear {
                network.getLoan()
            }
        }
    }
    
    class Network: ObservableObject {
        @Published var loan: Loan?  // <-- here
        
        func getLoan() {
            guard let url = URL(string: "https://absws.com/TmoAPI/v1/LSS.svc/GetLoan/1001"),
                  let payload = "".data(using: .utf8) else {
                return
            }
            
            var urlRequest = URLRequest(url: url)
            urlRequest.httpMethod = "GET"
            urlRequest.addValue("ABS", forHTTPHeaderField: "Token")
            urlRequest.addValue("World Mortgage Company", forHTTPHeaderField: "Database")
            urlRequest.httpBody = payload
            
            let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
                if let error = error {
                    print("Request error: ", error)
                    return
                }
    
                guard let response = response as? HTTPURLResponse else { return }
                
                if response.statusCode == 200 {
                    guard let data = data else { return }
                    DispatchQueue.main.async {
                        do {
                            let decoded = try JSONDecoder().decode(ApiData.self, from: data) // <-- here
                            self.loan = decoded.data
                        } catch let error {
                            print("Error decoding: ", error)
                        }
                    }
                }
            }
            dataTask.resume()
        }
    }
    
    struct ApiData: Codable {
        let data: Loan
        let errorMessage: String
        let errorNumber, status: Int
    
        enum CodingKeys: String, CodingKey {
            case data = "Data"
            case errorMessage = "ErrorMessage"
            case errorNumber = "ErrorNumber"
            case status = "Status"
        }
    }
    
    struct Loan: Codable {
        let type, account, accountNumber, accountType: String
        let applyAs, bankAddress, bankName, borrowerRecID: String
        let byLastName: String?
        let cdfiReporting: String
        let coBorrowers: [String]
        let customFields: [CustomField]
        let debitAmount, debitDueDay, emailAddress, emailFormat: String
        let frequency, individualID, individualName, isTemplate: String
        let losLoanRecID, nextDebitDate, notes: String
        let primaryBorrower: PrimaryBorrower
        let primaryProperty: String?
        let recID, routingNumber, serviceStatus, sortName: String
        let stopDate: String
        let sysTimeStamp: String?
        let terms: [String: String?]
        let useDebitAmount, wpcPin, wpcPublish: String
    
        enum CodingKeys: String, CodingKey {
            case type = "__type"
            case account = "Account"
            case accountNumber = "AccountNumber"
            case accountType = "AccountType"
            case applyAs = "ApplyAs"
            case bankAddress = "BankAddress"
            case bankName = "BankName"
            case borrowerRecID = "BorrowerRecID"
            case byLastName = "ByLastName"
            case cdfiReporting = "CDFIReporting"
            case coBorrowers = "CoBorrowers"
            case customFields = "CustomFields"
            case debitAmount = "DebitAmount"
            case debitDueDay = "DebitDueDay"
            case emailAddress = "EmailAddress"
            case emailFormat = "EmailFormat"
            case frequency = "Frequency"
            case individualID = "IndividualId"
            case individualName = "IndividualName"
            case isTemplate = "IsTemplate"
            case losLoanRecID = "LOSLoanRecID"
            case nextDebitDate = "NextDebitDate"
            case notes = "Notes"
            case primaryBorrower = "PrimaryBorrower"
            case primaryProperty = "PrimaryProperty"
            case recID = "RecID"
            case routingNumber = "RoutingNumber"
            case serviceStatus = "ServiceStatus"
            case sortName = "SortName"
            case stopDate = "StopDate"
            case sysTimeStamp = "SysTimeStamp"
            case terms = "Terms"
            case useDebitAmount = "UseDebitAmount"
            case wpcPin = "WPC_PIN"
            case wpcPublish = "WPC_Publish"
        }
    }
    
    struct CustomField: Codable {
        let name: String
        let tab: String
        let value: String
    
        enum CodingKeys: String, CodingKey {
            case name = "Name"
            case tab = "Tab"
            case value = "Value"
        }
    }
    
    struct PrimaryBorrower: Codable {
        let account, borrowerRecID, city, deliveryOptions: String
        let emailAddress, emailFormat, firstName, fullName: String
        let lastName, mi, phoneCell, phoneFax: String
        let phoneHome, phoneWork, placeOnHold, recID: String
        let rolodexPrint, salutation, sendLateNotices, sendPaymentReceipt: String
        let sendPaymentStatement, state, street, tin: String
        let tinType, zipCode: String
    
        enum CodingKeys: String, CodingKey {
            case account = "Account"
            case borrowerRecID = "BorrowerRecID"
            case city = "City"
            case deliveryOptions = "DeliveryOptions"
            case emailAddress = "EmailAddress"
            case emailFormat = "EmailFormat"
            case firstName = "FirstName"
            case fullName = "FullName"
            case lastName = "LastName"
            case mi = "MI"
            case phoneCell = "PhoneCell"
            case phoneFax = "PhoneFax"
            case phoneHome = "PhoneHome"
            case phoneWork = "PhoneWork"
            case placeOnHold = "PlaceOnHold"
            case recID = "RecID"
            case rolodexPrint = "RolodexPrint"
            case salutation = "Salutation"
            case sendLateNotices = "SendLateNotices"
            case sendPaymentReceipt = "SendPaymentReceipt"
            case sendPaymentStatement = "SendPaymentStatement"
            case state = "State"
            case street = "Street"
            case tin = "TIN"
            case tinType = "TINType"
            case zipCode = "ZipCode"
        }
    }