SwiftUI/Combine解析JSON报错:预期Array<Any>却找到字典如何解决
报错原因及解决方案
核心报错原因
你碰到的Expected to decode Array<Any> but found a dictionary instead错误和Combine无关,是解码类型和接口返回结构不匹配导致的:
- 你调用的是TMDB的单电影详情接口
https://api.themoviedb.org/3/movie/550,返回的JSON根结构是单个字典对象 - 但你在decode操作中指定的解析类型是
[FilmModel].self也就是数组类型,系统尝试把返回的字典解析成数组自然会报错。
其他隐藏解码问题
除了类型不匹配,你还有两处会导致解码失败的问题:
- 主模型
FilmModel的CodingKeys拼写错误:imdbID对应的rawValue写的是imbd_id,但JSON里的实际键是imdb_id,b和d顺序反了 - 所有子模型都没有配置CodingKeys做驼峰命名和下划线命名的映射:比如
ProductionCompany的logoPath对应JSON的logo_path、originCountry对应origin_country,ProductionCountry的iso3166_1对应iso_3166_1,SpokenLanguage的englishName对应english_name、iso639_1对应iso_639_1,没有映射的话这些字段都会解码失败。
修复步骤
1. 修正数据服务层的解码逻辑
把解码类型从数组改为单个FilmModel,收到返回值后再存入数组即可:
class FilmDataService { @Published var films: [FilmModel] = [] var filmSubscription: AnyCancellable? init() { getFilms() } private func getFilms() { guard let url = URL(string: "https://api.themoviedb.org/3/movie/550?api_key=<key>") else { return } filmSubscription = URLSession.shared.dataTaskPublisher(for: url) .subscribe(on: DispatchQueue.global(qos: .default)) .tryMap { (output) -> Data in guard let response = output.response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode < 300 else { throw URLError(.badServerResponse) } return output.data } // 这里把[FilmModel].self改成FilmModel.self .decode(type: FilmModel.self, decoder: JSONDecoder()) .receive(on: DispatchQueue.main) .sink { (completion) in switch completion { case .finished: break case .failure(let error): print(error) } } receiveValue: { [weak self] (returnedFilm) in // 把单个模型塞进数组 self?.films = [returnedFilm] self?.filmSubscription?.cancel() } } }
2. 修正FilmModel的CodingKeys拼写
enum CodingKeys: String, CodingKey { case adult case backdropPath = "backdrop_path" case budget case genres case homepage case id // 把原来的imbd_id改成imdb_id case imdbID = "imdb_id" case originalLanguage = "original_language" case originalTitle = "original_title" case overview case popularity case posterPath = "poster_path" case productionCompanies = "production_companies" case productionCountries = "production_countries" case releaseDate = "release_date" case revenue case runtime case spokenLanguages = "spoken_languages" case status, tagline, title case video case voteAverage = "vote_average" case voteCount = "vote_count" }
3. 给所有子模型添加CodingKeys映射
struct ProductionCompany: Codable { let id: Int let logoPath: String? let name, originCountry: String enum CodingKeys: String, CodingKey { case id case logoPath = "logo_path" case name case originCountry = "origin_country" } } struct ProductionCountry: Codable { let iso3166_1, name: String enum CodingKeys: String, CodingKey { case iso3166_1 = "iso_3166_1" case name } } struct SpokenLanguage: Codable { let englishName, iso639_1, name: String enum CodingKeys: String, CodingKey { case englishName = "english_name" case iso639_1 = "iso_639_1" case name } }
如果后续你需要获取电影列表,可以调用TMDB的/movie/popular这类列表接口,返回的根结构里results字段才是电影数组,到时候再对应调整解析逻辑即可。
内容的提问来源于stack exchange,提问作者trevordevelops
相关产品推荐
相关产品推荐

