Swift中如何处理不同JSON格式的HTTP响应?
Great question! Dealing with dynamic JSON structures based on user selection can get messy with endless if-else chains, but there's a clean, scalable approach using protocols and type mapping that avoids all that noise. Let's walk through it step by step.
Core Concept
We'll define a shared protocol for all category items, create model structs for each category that conform to this protocol, then map each category to its corresponding model type. This lets us decode dynamically without hardcoding conditional checks.
Step 1: Define a Shared Protocol
First, create a protocol that all your category models will follow. This ensures we have a common interface for displaying items in the TableView, even if the underlying JSON structures are different:
protocol CategoryItem: Decodable { // Define properties needed to display items in TableView var displayTitle: String { get } var displaySubtitle: String? { get } // Add any other shared behavior/properties here }
Step 2: Create Category-Specific Models
Next, build structs for each category that conform to the CategoryItem protocol. Each struct will handle its own JSON structure:
// Food category model struct FoodItem: CategoryItem { let name: String let price: String let rating: Double // Implement protocol requirements for display var displayTitle: String { name } var displaySubtitle: String? { "¥\(price) | \(rating)⭐" } } // Entertainment category model struct EntertainmentItem: CategoryItem { let activityName: String let location: String let startTime: String var displayTitle: String { activityName } var displaySubtitle: String? { "\(location) | \(startTime)" } } // Travel category model struct TravelItem: CategoryItem { let destination: String let duration: String let budget: String var displayTitle: String { destination } var displaySubtitle: String? { "\(duration) | \(budget)" } }
Step 3: Map Categories to Their Model Types
Use an enum to map each user-selectable category to its corresponding model type (and add helpful extras like API endpoints or display names):
enum Category: CaseIterable { case food, entertainment, travel // Map category to its associated model type var itemType: CategoryItem.Type { switch self { case .food: return FoodItem.self case .entertainment: return EntertainmentItem.self case .travel: return TravelItem.self } } // Optional: Add API endpoint for each category var apiEndpoint: String { switch self { case .food: return "/api/food" case .entertainment: return "/api/entertainment" case .travel: return "/api/travel" } } // Display name for the initial category TableView var displayName: String { switch self { case .food: return "美食" case .entertainment: return "娱乐" case .travel: return "旅行" } } }
Step 4: Dynamic Decoding & Data Fetching
Create a reusable function to fetch and decode data based on the selected category. This uses the mapped model type to decode dynamically:
func fetchItems(for category: Category, completion: @escaping (Result<[CategoryItem], Error>) -> Void) { // Replace with your actual base API URL guard let url = URL(string: "https://your-api-domain.com\(category.apiEndpoint)") else { completion(.failure(NSError(domain: "Invalid URL", code: -1, userInfo: nil))) return } URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { completion(.failure(error)) return } guard let data = data else { completion(.failure(NSError(domain: "No data received", code: -2, userInfo: nil))) return } do { // Dynamically decode using the category's mapped model type let items = try JSONDecoder().decode([category.itemType], from: data) as [CategoryItem] completion(.success(items)) } catch { completion(.failure(error)) } }.resume() }
Step 5: Use in Your TableView Controllers
In your second view controller, use the selected category to fetch data and populate the TableView using the shared CategoryItem interface:
class ItemsViewController: UIViewController, UITableViewDataSource { private let selectedCategory: Category private var items: [CategoryItem] = [] private let tableView = UITableView() init(category: Category) { self.selectedCategory = category super.init(nibName: nil, bundle: nil) title = category.displayName } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() setupTableView() fetchCategoryItems() } private func setupTableView() { tableView.frame = view.bounds tableView.dataSource = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: "ItemCell") view.addSubview(tableView) } private func fetchCategoryItems() { fetchItems(for: selectedCategory) { [weak self] result in DispatchQueue.main.async { switch result { case .success(let items): self?.items = items self?.tableView.reloadData() case .failure(let error): print("Failed to fetch items: \(error.localizedDescription)") // Add error handling (e.g., alert) here } } } } // MARK: UITableViewDataSource func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) let item = items[indexPath.row] cell.textLabel?.text = item.displayTitle cell.detailTextLabel?.text = item.displaySubtitle return cell } }
Why This Works Better Than If-Else Chains
- Scalability: Adding a new category only requires creating a new model struct and adding a case to the
Categoryenum—no need to modify existing decoding or TableView code. - Cleaner Code: All category-specific logic is encapsulated in its own struct, making your code easier to maintain and debug.
- Type Safety: The enum mapping ensures you're always decoding the correct model type for each category, avoiding runtime errors from mismatched types.
To answer your specific question about using a struct array: While you could store model types in an array, using an enum gives you explicit mapping between categories and their models, which is far more readable and less error-prone than relying on array indices.
内容的提问来源于stack exchange,提问作者Raja Tamil

