You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 Category enum—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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 03:24:08