swift - 与 API 交互时的 keyNotFound CodingKeys
问题描述
我正在使用 TMDB api,并且正在构建一个网络层来执行此操作。我也在关注一个教程,我很确定我正确地遵循了。我正在调用这个 API 端点:
{"page":1,
"results":[{"adult":false,"backdrop_path":"/fev8UFNFFYsD5q7AcYS8LyTzqwl.jpg","genre_ids":[16,28,35,10751],
"id":587807,
"original_language":"en","original_title":"Tom & Jerry",
"overview":"Jerry moves into New York City's finest hotel on the eve of the wedding of the century, forcing the desperate event planner to hire Tom to get rid of him. As mayhem ensues, the escalating cat-and-mouse battle soon threatens to destroy her career, the wedding, and possibly the hotel itself.",
"popularity":4136.493,
"poster_path":"/6KErczPBROQty7QoIsaa6wJYXZi.jpg",
"release_date":"2021-02-12",
"title":"Tom & Jerry",
"video":false,
"vote_average":8,
"vote_count":541}]
}
不幸的是,我收到了这个错误:
keyNotFound(CodingKeys(stringValue: "backdrop_path", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "results", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: \"backdrop_path\", intValue: nil) (\"backdrop_path\").", underlyingError: nil))
这适用于我的Resulting
结构中的每个键。
这是我的 ViewController 中的方法:
func fetchUpComing() {
let api = MovieDB.api
api.send(request: .upcomingMovies(completion: { result in
print("result", result)
switch result {
case .success(let page):
print(page.results)
self.upcomingMovies = page.results
var basicSection = MovieSection()
basicSection.numberOfItems = self.upcomingMovies.count
basicSection.upcomingItems = page.results
self.sections = [TitleSection(title: "Upcoming Movies"), basicSection]
self.setupCollectionView()
case .failure(let error): print(error)
}
}))
}
电影数据库:
struct MovieDB { // logic specific to the TMDB API
public static let baseURL = URL(string: "https://api.themoviedb.org/3/")!
public static var api: APIClient = {
let configuration = URLSessionConfiguration.default
let apiKey = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJmMzM2Yzk4MmY0MzdhMGQ1OTQ1MDFlY2U1YTA0ZmI3NiIsInN1YiI6IjYwM2ZjMTg5OTdlYWI0MDA3NzViYWIyMCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.J5hW42SMtm7RBeVGy9Z_Ex"
configuration.httpAdditionalHeaders = [
"Authorization": "Bearer"
]
return APIClient(configuration: configuration)
}()
public static let baseImageURL = URL(string: "https://image.tmdb.org/t/p/w500")!
}
public extension Result where Success == Data, Failure == Error {
func decoding<M: Model>(_ model: M.Type, completion: @escaping (Result<M, Error>) -> Void) {
// decode the JSON in the background and call the completion block on the main thread
DispatchQueue.global().async {
//Result’s flatMap() method takes the successful case (if it was successful) and applies your block. You can return a new Result that contains a successful value or an error.
let result = self.flatMap { data -> Result<M, Error> in
do {
let decoder = M.decoder
let model = try decoder.decode(M.self, from: data)
return .success(model)
} catch {
print("Error: \(error) with :\(String(data: data, encoding: .utf8))")
return .failure(error)
}
}
DispatchQueue.main.async {
completion(result)
}
}
}
API客户端:
private let session: URLSession
init(configuration: URLSessionConfiguration) {
session = URLSession(configuration: configuration)
}
public func send(request: Request) {
let urlRequest = request.builder.toURLRequest()
let task = session.dataTask(with: urlRequest) { data, response, error in
let result: Result<Data, Error>
if let error = error {
result = .failure(error)
} else {
result = .success(data ?? Data())
}
DispatchQueue.main.async {
request.completion(result)
}
}
task.resume()
}
}
要求:
public struct Request {
let builder: RequestBuilder
let completion: (Result<Data, Error>) -> Void
init(builder: RequestBuilder, completion: @escaping (Result<Data, Error>) -> Void) {
self.builder = builder
self.completion = completion
}
public static func basic(method: HTTPMethod = .get, baseURL: URL, path: String, params: [URLQueryItem]? = nil, completion: @escaping (Result<Data, Error>) -> Void) -> Request {
let builder = BasicRequestBuilder(method: method, baseURL: baseURL, path: path, params: params)
return Request(builder: builder, completion: completion)
}
}
extension Request {
static func popularMovies(completion: @escaping (Result<PagedResults<Movie>, Error>) -> Void) -> Request {
Request.basic(baseURL: MovieDB.baseURL, path: "discover/movie", params: [
URLQueryItem(name: "sort_by", value: "popularity.desc")
]) { result in
result.decoding(PagedResults<Movie>.self, completion: completion) }
}
static func upcomingMovies(completion: @escaping (Result<Upcoming<Resulting>, Error>) -> Void) -> Request {
Request.basic(baseURL: MovieDB.baseURL, path: "movie/upcoming", params: [
URLQueryItem(name: "sort_by", value: "popularity.desc")
]) { result in
result.decoding(Upcoming<Resulting>.self, completion: completion) }
}
}
请求生成器:
public enum HTTPMethod: String {
case get
case post
case put
case delete
}
public protocol RequestBuilder {
var method: HTTPMethod { get }
var baseURL: URL { get }
var path: String { get }
var params: [URLQueryItem]? { get }
var headers: [String: String] { get }
func toURLRequest() -> URLRequest
}
public extension RequestBuilder {
func toURLRequest() -> URLRequest {
var components = URLComponents(url: baseURL.appendingPathComponent(path), resolvingAgainstBaseURL: false)!
components.queryItems = params
let url = components.url!
var request = URLRequest(url: url)
request.allHTTPHeaderFields = headers
request.httpMethod = method.rawValue.uppercased()
return request
}
}
struct BasicRequestBuilder: RequestBuilder {
var method: HTTPMethod
var baseURL: URL
var path: String
var params: [URLQueryItem]?
var headers: [String: String] = [:]
}
这是我在解码中使用的结构:
import Foundation
struct Movie: Model, Hashable {
let id: Int
let title: String
let posterPath: String
let releaseDate: String
}
struct PagedResults<T: Model>: Model {
let page: Int
let totalPages: Int
let results: [T]
}
extension PagedResults {
static var decoder: JSONDecoder { T.decoder }
}
struct Upcoming<T: Model>: Model {
let dates: Dates
let page: Int
let results: [Resulting]
let totalPages, totalResults: Int
enum CodingKeys: String, CodingKey {
case dates, page, results
case totalPages = "total_pages"
case totalResults = "total_results"
}
}
extension Upcoming {
static var decoder: JSONDecoder { T.decoder }
}
// MARK: - Dates
struct Dates: Codable {
let maximum, minimum: String
}
// MARK: - Result
struct Resulting: Model, Hashable{
let adult: Bool
let backdropPath: String
let genreIDS: [Int]
let id: Int
let originalLanguage: OriginalLanguage
let originalTitle, overview: String
let popularity: Double
let posterPath, releaseDate, title: String
let video: Bool
let voteAverage: Double
let voteCount: Int
enum CodingKeys: String, CodingKey {
case adult
case backdropPath = "backdrop_path"
case genreIDS = "genre_ids"
case id
case originalLanguage = "original_language"
case originalTitle = "original_title"
case overview, popularity
case posterPath = "poster_path"
case releaseDate = "release_date"
case title, video
case voteAverage = "vote_average"
case voteCount = "vote_count"
}
}
enum OriginalLanguage: String, Codable {
case en = "en"
case es = "es"
case ja = "ja"
}
解决方案
推荐阅读
- excel - 我需要使用 excel 公式获取输出
- amazon-redshift - 在 redshift plpgsql 的 except 块中插入语句
- jira - 在 Jira 中组织任务审查(检查、验证)
- express - graphQL中具有联合类型的空对象
- android - RecyclerView 展开/折叠
- java - 强制 Maven Wagon 插件发布而不是 PUT
- python - 使用多个元组进行 NumPy 切片
- python-3.x - python请求中的会话问题
- c# - 使用 GetPositionAsync Geolocator 时检测到异常
- reactjs - 无法删除 React NavLink 中的活动类