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

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()
}

解决方案

核心问题分析

  1. 不存在的key:API返回的JSON中没有details字段,你定义的MovieFinder强行解析该字段导致报错。
  2. 结构不匹配: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 04:17:16