Swift解析JSON响应时获取嵌套字段到Label遇解包崩溃问题
Hey there, that crash is a classic case of unsafe force unwrapping biting you—let's break down what's going wrong and fix it properly, plus get those values into your labels.
What's Causing the Crash?
Your line let billerDetails = json["billerResponse"] as! String uses as!, which crashes if json["billerResponse"] is either missing from the response or isn't a String type. Even though your sample JSON shows it's a stringified JSON object, we should never rely on force unwraps for network responses—servers can send unexpected data!
Complete Safe Implementation
Here's a revised billerFetchService method that handles all edge cases, parses the nested JSON correctly, and updates your labels safely:
func billerFetchService() { // Replace with your actual API endpoint guard let apiUrl = URL(string: "https://your-biller-api.com/fetch-bill") else { print("Invalid API URL") return } // Set up POST request var request = URLRequest(url: apiUrl) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") // Add your request parameters (adjust as needed for your API) let requestParams: [String: Any] = [ "serviceNumber": "116515M025007621", // Example from your sample data // Add other required params here ] // Encode request body do { request.httpBody = try JSONSerialization.data(withJSONObject: requestParams, options: []) } catch { print("Failed to encode request body: \(error.localizedDescription)") return } // Execute the request let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in guard let self = self else { return } // Handle network errors if let error = error { print("Network error occurred: \(error.localizedDescription)") return } // Validate response and data guard let responseData = data, let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { print("Invalid response or no data received") return } // Parse outer JSON response do { guard let outerJson = try JSONSerialization.jsonObject(with: responseData) as? [String: Any], let responseDict = outerJson["response"] as? [String: Any] else { print("Failed to parse outer response structure") return } // Safely get the billerResponse string (NO force unwrap!) guard let billerResponseString = responseDict["billerResponse"] as? String else { print("billerResponse is missing or not a string") return } // Parse the nested JSON string inside billerResponse guard let billerResponseData = billerResponseString.data(using: .utf8) else { print("Failed to convert billerResponse string to data") return } let billerDetails = try JSONSerialization.jsonObject(with: billerResponseData) as? [String: Any] // Update labels ON MAIN THREAD (UI changes must happen here!) DispatchQueue.main.async { // Use nil coalescing to show a default if fields are missing self.customerNameLabel.text = billerDetails?["customerName"] as? String ?? "Unknown" self.amountLabel.text = { if let amount = billerDetails?["amount"] as? Int { return String(format: "₹%.2f", Double(amount)) // Adjust currency symbol as needed } return "0.00" }() self.dueDateLabel.text = billerDetails?["dueDate"] as? String ?? "No due date" self.billNumberLabel.text = billerDetails?["billNumber"] as? String ?? "No bill number" self.serviceNumberLabel.text = billerDetails?["fieldValue"] as? String ?? "No service number" self.billerNameLabel.text = billerDetails?["billerName"] as? String ?? "Unknown biller" } } catch { print("Parsing error: \(error.localizedDescription)") } } task.resume() }
Key Fixes & Best Practices:
- Safe Optional Handling: Replaced all
as!withguard letand optional bindings to avoid crashes if data is missing or malformed. - Double Parsing: Since
billerResponseis a stringified JSON object, we first convert it toData, then parse it into a dictionary. - Main Thread UI Updates: Always update UI elements (like labels) on the main thread using
DispatchQueue.main.async—this prevents weird UI glitches or crashes. - Graceful Fallbacks: Used
??to provide default values if any field is missing, so your app doesn't show empty labels. - Error Logging: Added detailed error messages to help you debug if something goes wrong with the network or parsing.
Bonus: Cleaner Parsing with Codable
For a more maintainable solution as your app grows, use Swift's Codable protocol instead of manual dictionary parsing. Here's how:
1. Define Codable Models
// Outer response structure struct APIResponse: Codable { let response: ResponseContent } struct ResponseContent: Codable { let responseReason: String let billDetails: String? let billerResponse: String } // Biller details model struct BillerDetails: Codable { let customerName: String let amount: Int let dueDate: String let billNumber: String let fieldName: String let fieldValue: String let billerName: String // Add other fields you need, mark as optional if they might be missing }
2. Updated Parsing Logic
// Inside the data task completion handler: do { // Parse outer response let apiResponse = try JSONDecoder().decode(APIResponse.self, from: responseData) // Parse nested billerResponse string guard let billerResponseData = apiResponse.response.billerResponse.data(using: .utf8) else { print("Failed to convert billerResponse to data") return } let billerDetails = try JSONDecoder().decode(BillerDetails.self, from: billerResponseData) // Update labels on main thread DispatchQueue.main.async { self.customerNameLabel.text = billerDetails.customerName self.amountLabel.text = String(format: "₹%.2f", Double(billerDetails.amount)) self.dueDateLabel.text = billerDetails.dueDate self.billNumberLabel.text = billerDetails.billNumber self.serviceNumberLabel.text = billerDetails.fieldValue self.billerNameLabel.text = billerDetails.billerName } } catch { print("Codable parsing error: \(error)") }
This is much cleaner and less error-prone—you'll get compile-time checks if you misspell field names, and adding new fields is just a matter of updating the model.
内容的提问来源于stack exchange,提问作者Swift

