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

SwiftUI JSON解析报错:期望数组却得到字典问题求助

问题解决及优化方案

核心错误分析

  1. 类型不匹配错误:你调用的TMDB /movie/550 接口返回的是单个电影对象(JSON字典),但代码里尝试解码成[Movie].self(数组类型),导致类型不匹配。
  2. 字段映射错误:TMDB返回的日期字段是release_date,但结构体里写的是releaseDate,未做蛇形命名到驼峰的映射,解码失败。
  3. URL缺失API密钥:代码里的urlString未包含api_key参数,实际请求会返回401错误,导致数据格式不正确。

修正后的代码

MoviesFetcher.swift

import Foundation

struct Movie: Codable {
    let originalTitle: String
    let releaseDate: String
    
    // 手动指定字段映射,或使用下方keyDecodingStrategy二选一
    enum CodingKeys: String, CodingKey {
        case originalTitle = "original_title"
        case releaseDate = "release_date"
    }
}

class MovieFetcher {
    // API密钥建议存入Info.plist,避免硬编码
    private let apiKey = "d2ea758c1787f6f8715df6713125a180"
    
    func fetchSingleMovie(completion: @escaping (Result<Movie, Error>) -> Void) {
        let urlString = "https://api.themoviedb.org/3/movie/550?api_key=\(apiKey)"
        
        guard let url = URL(string: urlString) else {
            completion(.failure(NSError(domain: "InvalidURL", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的URL"])))
            return
        }
        
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }
            
            guard let data = data else {
                completion(.failure(NSError(domain: "NoData", code: -2, userInfo: [NSLocalizedDescriptionKey: "未获取到数据"])))
                return
            }
            
            do {
                let decoder = JSONDecoder()
                // 若不用CodingKeys,可启用此策略自动转换蛇形到驼峰命名
                // decoder.keyDecodingStrategy = .convertFromSnakeCase
                let movie = try decoder.decode(Movie.self, from: data)
                completion(.success(movie))
            } catch {
                completion(.failure(error))
            }
        }
        
        task.resume()
    }
}

ContentView.swift

import SwiftUI

struct ContentView: View {
    @State private var movie: Movie?
    @State private var errorMessage: String?
    
    var body: some View {
        VStack(spacing: 20) {
            if let errorMessage = errorMessage {
                Text("出错了:\(errorMessage)")
                    .foregroundColor(.red)
            } else if let movie = movie {
                VStack(alignment: .leading) {
                    Text("电影标题:\(movie.originalTitle)")
                        .font(.title)
                    Text("上映日期:\(movie.releaseDate)")
                        .font(.subheadline)
                }
            } else {
                ProgressView("加载中...")
            }
        }
        .padding()
        .onAppear {
            MovieFetcher().fetchSingleMovie { result in
                DispatchQueue.main.async {
                    switch result {
                    case .success(let fetchedMovie):
                        self.movie = fetchedMovie
                    case .failure(let error):
                        self.errorMessage = error.localizedDescription
                    }
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

优化建议

  1. 抛弃全局变量:原代码的movieArray是全局变量,多线程环境下易出现数据竞争,改用completion闭包返回结果更安全。
  2. 用Result类型处理状态:通过Result<Movie, Error>明确区分成功/失败状态,错误处理更清晰。
  3. 强制主线程更新UI:网络请求在后台线程执行,更新UI必须切回主线程,代码中用DispatchQueue.main.async保证操作安全。
  4. 安全管理API密钥:不要硬编码密钥,建议添加到Info.plist,通过Bundle.main.object(forInfoDictionaryKey: "TMDB_API_KEY")读取。
  5. 列表需求适配:若要获取电影数组,需调用TMDB列表接口(如/movie/popular),此时解码成[Movie].self才正确,同步修改Fetcher方法和ContentView状态即可。

内容的提问来源于stack exchange,提问作者Gonçalo Ramos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 22:15:19