SwiftUI获取含双数组的JSON API并在TabBar展示失败排查
SwiftUI中API数据解析失败:格式错误问题解决
核心问题分析
- 请求方式错误:错误拼接了API路径,且未按照要求用
x-www-form-urlencoded格式传递参数,导致返回的JSON结构与模型不匹配。 - 模型解码问题:
event_chargable_or_not字段返回字符串(如"0")或null,直接映射到Bool类型会解码失败;日期解析未指定locale,易因地区差异导致解析出错。
修正步骤
1. 修复API请求逻辑
直接使用提供的API地址,通过POST请求传递event_type参数,符合接口要求的x-www-form-urlencoded格式:
class ApiService { static let baseUrl = "https://dev.myhss.org.uk/api/v1/events/eventlist" static let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" formatter.locale = Locale(identifier: "en_US_POSIX") // 避免地区差异导致解析失败 return formatter }() static func fetchEvents(type: EventType, completion: @escaping (Result<EventListing, Error>) -> Void) { guard let url = URL(string: baseUrl) else { completion(.failure(ApiError.invalidUrl)) return } // 构建请求参数 let eventTypeValue = type == .upcoming ? "1" : "2" let parameters = "event_type=\(eventTypeValue)" let requestBody = parameters.data(using: .utf8) var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type") request.httpBody = requestBody URLSession.shared.dataTask(with: request) { data, response, error in if let error = error { completion(.failure(error)) return } guard let data = data else { completion(.failure(ApiError.noData)) return } // 打印原始数据方便调试 if let rawJson = String(data: data, encoding: .utf8) { print("返回JSON: \(rawJson)") } do { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(dateFormatter) let eventListing = try decoder.decode(EventListing.self, from: data) completion(.success(eventListing)) } catch let error { print("解码错误详情: \(error)") // 打印详细错误便于排查 completion(.failure(error)) } }.resume() } enum ApiError: Error, LocalizedError { case invalidUrl case noData var errorDescription: String? { switch self { case .invalidUrl: return "无效的URL地址" case .noData: return "未获取到数据" } } } }
2. 修正Event模型的解码逻辑
针对event_chargable_or_not字段的字符串/空值情况,自定义解码逻辑:
struct Event: Codable, Identifiable { var id: String var title: String var startDate: Date var endDate: Date var imageURL: URL? var isChargeable: Bool? // 改为可选类型处理null情况 enum CodingKeys: String, CodingKey { case id = "event_id" case title = "event_title" case startDate = "event_start_date" case endDate = "event_end_date" case imageURL = "event_img" case isChargeable = "event_chargable_or_not" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(String.self, forKey: .id) title = try container.decode(String.self, forKey: .title) startDate = try container.decode(Date.self, forKey: .startDate) endDate = try container.decode(Date.self, forKey: .endDate) imageURL = try container.decodeIfPresent(URL.self, forKey: .imageURL) // 处理字段的字符串/空值逻辑 if let chargeableStr = try container.decodeIfPresent(String.self, forKey: .isChargeable) { isChargeable = chargeableStr == "1" } else { isChargeable = nil } } }
3. 其他优化点
- 解码失败时打印详细错误信息,而非仅本地化描述,便于定位具体字段的解析问题。
- 给DateFormatter指定固定locale,避免不同地区的日期格式解析差异。
测试验证
修改完成后,切换Upcoming或Past标签页时,即可正确获取并解析API数据,显示事件标题。若仍有问题,可通过控制台打印的原始JSON和解码错误详情继续排查。
内容的提问来源于stack exchange,提问作者user20925367
相关产品推荐
相关产品推荐

