Swift解析The MovieDB API根对象JSON时keyNotFound错误的解决
问题:The MovieDB API JSON解析keyNotFound错误处理
我正在使用The MovieDB API获取并解析JSON数据,该JSON以{开头,为单个对象结构,无外层根节点。自定义了包含details字段的MovieFinder结构体进行解析时触发了keyNotFound错误,错误信息如下:
"keyNotFound(CodingKeys(stringValue: "details", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"details\", intValue: nil) (\"details\").", underlyingError: nil))"
API返回JSON示例
{ "id": 328, "name": "Jurassic Park Collection", "overview": "An American science fiction adventure film series based on the novel of the same name by Michael Crichton. The films center on the fictional Isla Nublar near Costa Rica in the Central American Pacific Coast, where a billionaire philanthropist and a small team of genetic scientists have created an amusement park of cloned dinosaurs.", "poster_path": "/jcUXVtJ6s0NG0EaxllQCAUtqdr0.jpg", "backdrop_path": "/njFixYzIxX8jsn6KMSEtAzi4avi.jpg", "parts": [ { "adult": false, "backdrop_path": "/79bJL9ydAMYVltuNTt4VhxORqIz.jpg", "id": 329, "title": "Jurassic Park", "original_language": "en", "original_title": "Jurassic Par", "overview": "A wealthy entrepreneur secretly creates a theme park featuring living dinosaurs drawn from prehistoric DNA. Before opening day, he invites a team of experts and his two eager grandchildren to experience the park and help calm anxious investors. However, the park is anything but amusing as the security systems go off-line and the dinosaurs escape.", "poster_path": "/oU7Oq2kFAAlGqbU4VoAE36g4hoI.jpg", "media_type": "movie", "genre_ids": [ 12, 878 ], "popularity": 27.914, "release_date": "1993-06-11", "video": false, "vote_average": 7.9, "vote_count": 15644 } ] }
我的代码
MovieFinder结构体
struct MovieFinder: Decodable { let details : [MovieModel] }
数据模型
struct MovieModel: Decodable, Identifiable { var id: Int var name: String var overview: String var posterPath: String var parts: Part enum CodingKeys: String, CodingKey { case posterPath = "poster_path" case id case name case overview case parts } } struct Part: Decodable, Identifiable { var id: Int var title: String var overview: String var posterPath: String var releaseDate: String var voteAverage: Double enum CodingKeys: String, CodingKey { case posterPath = "poster_path" case releaseDate = "release_date" case voteAverage = "vote_average" case id case title case overview } }
API管理器
struct MovieAPIManager { let apiKey = func MovieSearch() async -> [MovieModel] { if let url = URL(string: "https://api.themoviedb.org/3/collection/328-jurassic-park-collection?api_key=apiKey") { let request = URLRequest(url: url) do { let (data, response) = try await URLSession.shared.data(for: request) let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 let result = try decoder.decode(MovieFinder.self, from: data) return result.details } catch { print(error) } } return [MovieModel]() } }
视图代码
struct MoviesCard: View { @State var movies = [MovieModel]() var dataService = MovieAPIManager() var body: some View { NavigationView { VStack { List(movies) { movie in VStack { Text(movie.parts.title) } } .task { movies = await dataService.MovieSearch() } } } } } #Preview { MoviesCard() }
解决方案
核心问题分析
- 不存在的key:API返回的JSON中没有
details字段,你定义的MovieFinder强行解析该字段导致报错。 - 结构不匹配:API里的
parts是数组类型,但你的MovieModel将其定义为单个Part对象,类型不兼容。
修正步骤
1. 删除无用的MovieFinder结构体
直接解析API返回的集合对象,无需额外外层包装。
2. 修正数据模型结构
让模型完全匹配JSON结构:
// 对应API返回的集合对象 struct MovieCollection: Decodable, Identifiable { let id: Int let name: String let overview: String let posterPath: String? // 标记为可选,避免字段缺失导致解析失败 let parts: [Movie] enum CodingKeys: String, CodingKey { case posterPath = "poster_path" case id, name, overview, parts } } // 对应parts数组中的单个电影 struct Movie: Decodable, Identifiable { let id: Int let title: String let overview: String let posterPath: String? let releaseDate: String? let voteAverage: Double enum CodingKeys: String, CodingKey { case posterPath = "poster_path" case releaseDate = "release_date" case voteAverage = "vote_average" case id, title, overview } }
3. 修正API管理器
直接解析MovieCollection并返回其中的电影数组:
struct MovieAPIManager { let apiKey = "你的实际API密钥" // 替换为你的密钥 func fetchMovieCollection() async -> [Movie] { // 简化URL,用集合ID直接访问 guard let url = URL(string: "https://api.themoviedb.org/3/collection/328?api_key=\(apiKey)") else { return [] } do { let (data, _) = try await URLSession.shared.data(from: url) let decoder = JSONDecoder() // 可选:自动处理下划线转驼峰,可省略CodingKeys decoder.keyDecodingStrategy = .convertFromSnakeCase let collection = try decoder.decode(MovieCollection.self, from: data) return collection.parts } catch { print("解析错误:\(error)") return [] } } }
4. 修正视图代码
更新数组类型和显示逻辑:
struct MoviesCard: View { @State var movies = [Movie]() let dataService = MovieAPIManager() var body: some View { // iOS16+推荐用NavigationStack替代NavigationView NavigationStack { List(movies) { movie in VStack(alignment: .leading) { Text(movie.title) .font(.headline) Text(movie.overview) .font(.caption) .lineLimit(2) } } .navigationTitle("侏罗纪公园系列") .task { movies = await dataService.fetchMovieCollection() } } } } #Preview { MoviesCard() }
额外优化建议
- 给可能缺失的字段添加
?标记,提升解析容错性; - 使用
decoder.keyDecodingStrategy = .convertFromSnakeCase可以自动转换命名风格,减少手动编写CodingKeys的工作量; - 用字符串插值注入API密钥,避免硬编码错误;
- 适配iOS新版本的
NavigationStack替代旧版NavigationView。
内容的提问来源于stack exchange,提问作者AlTheBeast
相关产品推荐
相关产品推荐

