SwiftUI中带认证的GET请求无法获取并解析REST API任务列表数据问题求助
SwiftUI中带认证的GET请求无法获取并解析REST API任务列表数据问题求助
嗨,我帮你看了下代码和返回的JSON响应,发现几个导致解析失败的关键问题,一个个来给你说:
首先是字段映射与类型不匹配的问题:
- JSON里的主键是
ID(字符串类型,比如"013"),但你的Job结构体里定义的是id: Int,既没有设置CodingKeys映射字段名,类型也不匹配,这肯定会解码失败; - JSON里的字段都是下划线分隔命名(比如
job_addr_1、job_client_job_no),但Job结构体用的是驼峰命名(jobAddr1、jobClientJobNo),默认的JSONDecoder不会自动转换这种命名风格,需要手动映射或者设置解码器策略;
然后是可选性与类型错误:
- 很多日期字段在JSON里是
null或者空字符串(比如job_contractor_instructed是null,job_total_quote是空字符串),但你在Job里直接定义为非可选的Date类型,解码器无法把null/空字符串转成Date,会抛出错误; - 像
job_type在JSON里是null,但你定义的jobType: Int是非可选类型,同样会解码失败; - 部分字段比如
job_eicr_expiry_info在JSON里是空字符串,不是日期格式,你却定义成了Date类型,这也会导致解码失败;
最后是日期解码策略缺失:
JSON里的日期是yyyy-MM-dd格式的字符串,默认的JSONDecoder不会自动把这种字符串转成Date类型,需要手动设置日期解码策略。
下面是修改后的完整解决方案:
1. 修正Job结构体的字段映射与类型
struct Job: Codable, Identifiable { // 匹配JSON的字符串类型ID let id: String let jobUniqid: String? let jobClientJobNo: String? let jobPriority: String? let jobQuoteNo: String? let jobInvoiceNo: String? // 把非可选Int改成可选String(JSON里是字符串格式的数字) let jobStatus: String? let jobAgentId: String? let jobAddr1: String? let jobAddr2: String? let jobTownCity: String? let jobCounty: String? let jobPostcode: String? let jobContactName1: String? let jobContactNo1: String? let jobContactEmail1: String? let jobContactName2: String? let jobContactNo2: String? let jobContactEmail2: String? let jobSentBy: String? // 改成可选Int,匹配JSON的null值 let jobType: Int? let jobContractorId: String? let jobDescription: String? let jobDescriptionShort: String? let jobBudget: String? let jobContractorBudget: String? let jobCreatedDate: String? let jobAgentNotes: String? let jobContractorNotes: String? let jobDynamoNotes : String? let jobNotes : String? // 所有日期字段改成可选Date let jobContractorInstructed : Date? let jobContractorAction : String? let jobContractorAccepted : Date? let jobContractorAcceptedDate : Date? let jobContractorDescription : String? let jobContractorPriceAgreed : String? let jobBookedDate : Date? let jobQuotedDate : Date? let jobAcceptedDate : Date? let jobStartDate : Date? let jobCompletionDate : Date? let jobInvoiceDate : Date? let jobOnholdDate : Date? let jobCancelledDate : Date? // 改成可选String,匹配JSON的空字符串 let jobTotalQuote : String? let jobContractorCutPreVat : String? let jobContractorCut : String? let jobDynamoCut : String? let jobAgentsCut : String? let jobSophieCut : String? let jobInvoiceAmount : String? let jobPreVatAmount : String? let jobNetAmount : String? let jobVatAmount : String? let jobGscExpiryDate : Date? let jobFloorPlan : String? let jobVatCharged : String? let jobVatCollected : String? let jobInvoiceNotes : String? let jobContractorPaid : String? let jobInvoicePaid : String? let jobInvoicePaidEmail : String? let jobAgentsVat : String? let jobVatLiability : String? let jobContractorInvoice : String? let jobContractorInvoiceNo : String? let jobContactReason : String? let jobQuoteDescription : String? let jobContractorProceed : Date? let jobContractorProceedReceipt : String? let jobSendSms : Int? let jobDetailsOfGsc : String? let jobDetailsId : Int? let jobIntroducerId : Int? let jobIntroducerCommissionPaid : Date? let jobEicrExpiryDate : Date? // 改成可选String,匹配JSON的空字符串 let jobEicrExpiryInfo : String? let jobEpcExpiryInfo : String? let jobGscExpiryInfo : String? let jobEpcExpiryDate : Date? let jobOnholdReason : String? let jobPartPaymentReceived : String? let jobContractorPartPaymentPaid : String? let jobContractorBalance : String? let jobQuoteRejectedDate : Date? let jobReminderSent : Data? let jobDepositInvoiced : String? let jobAmberList : String? // 手动映射JSON字段名 enum CodingKeys: String, CodingKey { case id = "ID" case jobUniqid = "job_uniqid" case jobClientJobNo = "job_client_job_no" case jobPriority = "job_priority" case jobQuoteNo = "job_quote_no" case jobInvoiceNo = "job_invoice_no" case jobStatus = "job_status" case jobAgentId = "job_agent_id" case jobAddr1 = "job_addr_1" case jobAddr2 = "job_addr_2" case jobTownCity = "job_town_city" case jobCounty = "job_county" case jobPostcode = "job_postcode" case jobContactName1 = "job_contact_name_1" case jobContactNo1 = "job_contact_no_1" case jobContactEmail1 = "job_contact_email_1" case jobContactName2 = "job_contact_name_2" case jobContactNo2 = "job_contact_no_2" case jobContactEmail2 = "job_contact_email_2" case jobSentBy = "job_sent_by" case jobType = "job_type" case jobContractorId = "job_contractor_id" case jobDescription = "job_description" case jobDescriptionShort = "job_description_short" case jobBudget = "job_budget" case jobContractorBudget = "job_contractor_budget" case jobCreatedDate = "job_created_date" case jobAgentNotes = "job_agent_notes" case jobContractorNotes = "job_contractor_notes" case jobDynamoNotes = "job_dynamo_notes" case jobNotes = "job_notes" case jobContractorInstructed = "job_contractor_instructed" case jobContractorAction = "job_contractor_action" case jobContractorAccepted = "job_contractor_accepted" case jobContractorAcceptedDate = "job_contractor_accepted_date" case jobContractorDescription = "job_contractor_description" case jobContractorPriceAgreed = "job_contractor_price_agreed" case jobBookedDate = "job_booked_date" case jobQuotedDate = "job_quoted_date" case jobAcceptedDate = "job_accepted_date" case jobStartDate = "job_start_date" case jobCompletionDate = "job_completion_date" case jobInvoiceDate = "job_invoice_date" case jobOnholdDate = "job_onhold_date" case jobCancelledDate = "job_cancelled_date" case jobTotalQuote = "job_total_quote" case jobContractorCutPreVat = "job_contractor_cut_pre_vat" case jobContractorCut = "job_contractor_cut" case jobDynamoCut = "job_dynamo_cut" case jobAgentsCut = "job_agents_cut" case jobSophieCut = "job_sophie_cut" case jobInvoiceAmount = "job_invoice_amount" case jobPreVatAmount = "job_pre_vat_amount" case jobNetAmount = "job_net_amount" case jobVatAmount = "job_vat_amount" case jobGscExpiryDate = "job_gsc_expiry_date" case jobFloorPlan = "job_floor_plan" case jobVatCharged = "job_vat_charged" case jobVatCollected = "job_vat_collected" case jobInvoiceNotes = "job_invoice_notes" case jobContractorPaid = "job_contractor_paid" case jobInvoicePaid = "job_invoice_paid" case jobInvoicePaidEmail = "job_invoice_paid_email" case jobAgentsVat = "job_agents_vat" case jobVatLiability = "job_vat_liability" case jobContractorInvoice = "job_contractor_invoice" case jobContractorInvoiceNo = "job_contractor_invoice_no" case jobContactReason = "job_contact_reason" case jobQuoteDescription = "job_quote_description" case jobContractorProceed = "job_contractor_proceed" case jobContractorProceedReceipt = "job_contractor_proceed_receipt" case jobSendSms = "job_send_sms" case jobDetailsOfGsc = "job_details_of_gsc" case jobDetailsId = "job_details_id" case jobIntroducerId = "job_introducer_id" case jobIntroducerCommissionPaid = "job_introducer_commission_paid" case jobEicrExpiryDate = "job_eicr_expiry_date" case jobEicrExpiryInfo = "job_eicr_expiry_info" case jobEpcExpiryInfo = "job_epc_expiry_info" case jobGscExpiryInfo = "job_gsc_expiry_info" case jobEpcExpiryDate = "job_epc_expiry_date" case jobOnholdReason = "job_onhold_reason" case jobPartPaymentReceived = "job_part_payment_received" case jobContractorPartPaymentPaid = "job_contractor_part_payment_paid" case jobContractorBalance = "job_contractor_balance" case jobQuoteRejectedDate = "job_quote_rejected_date" case jobReminderSent = "job_reminder_sent" case jobDepositInvoiced = "job_deposit_invoiced" case jobAmberList = "job_amber_list" } }
2. 修正loadData方法的解码逻辑
func loadData() { guard let url = URL(string: "https://api.badsey.app/\(MemberCheck)/API/dp_jobs/read.php?pageno=1&pagesize=1") else { print("Invalid URL") DispatchQueue.main.async { self.alertMessage = "Invalid API URL" self.showAlert = true } return } var request = URLRequest(url: url) request.httpMethod = "GET" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.setValue("Bearer \(TokenCheck)", forHTTPHeaderField: "Authorization") URLSession.shared.dataTask(with: request) { data, response, error in // 先处理错误和空数据的情况,避免强制解包崩溃 guard let data = data, error == nil else { let errorMsg = error?.localizedDescription ?? "No data received from API" print("Request error: \(errorMsg)") DispatchQueue.main.async { self.alertMessage = errorMsg self.showAlert = true } return } print("---> API Response: \(String(data: data, encoding: .utf8) ?? "Invalid UTF8 data")") do { let decoder = JSONDecoder() // 设置日期解码策略,匹配JSON的yyyy-MM-dd格式 let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" decoder.dateDecodingStrategy = .formatted(dateFormatter) let decodedResponse = try decoder.decode(JobAPI.self, from: data) DispatchQueue.main.async { self.jobs = decodedResponse.document.records } } catch { let decodeError = "Decoding failed: \(error.localizedDescription)" print(decodeError) DispatchQueue.main.async { self.alertMessage = decodeError self.showAlert = true } } }.resume() }
额外注意事项
- 不要强制解包
data!,如果网络请求失败或返回空数据会直接崩溃,一定要先判断数据是否存在; - 可以通过alert展示错误信息,方便调试;
- 如果后续遇到新的解码错误,可以根据控制台打印的错误信息,针对性调整字段的类型或可选性。
按照上面的修改,应该就能正常解析API数据并展示在列表里了,有问题随时再问!
备注:内容来源于stack exchange,提问作者Badsey
相关产品推荐
相关产品推荐

