Swift中Post泛型Codable API响应异常问题排查
我创建了APINetworkManagerAll类并实现了serviceCall泛型方法,在ViewController中调用postGenericCall2()发起Post请求,虽能收到响应,但传入的参数在响应中无法被正确解析,最终触发解码错误。使用Postman传入相同参数可正常获取预期响应。
相关代码
RequestObjectAll定义与APINetworkManagerAll实现
struct RequestObjectAll { var params: [String: Any]? = nil var method: HTTPMethod var urlPath: String var isTokenNeeded: Bool var isLoaderNeed: Bool = false var isErrorNeed: Bool = false var vc: UIViewController? } class APINetworkManagerAll: NSObject { static let sharedInstance = APINetworkManagerAll() fileprivate override init() { super.init() } func serviceCall<T: Decodable>(requestObject: RequestObjectAll, completion: @escaping (Result<T, Error>) -> Void) { if requestObject.isLoaderNeed { requestObject.vc?.showLoader() } guard let url = URL(string: requestObject.urlPath) else { if requestObject.isLoaderNeed { requestObject.vc?.hideLoader() } completion(.failure(NetworkError.invalidURL)) return } var urlRequest = URLRequest(url: url) urlRequest.httpMethod = requestObject.method.rawValue guard let httpBody = try? JSONSerialization.data(withJSONObject: requestObject.params ?? ["": ""], options: []) else { return } urlRequest.httpBody = httpBody let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in if requestObject.isLoaderNeed { requestObject.vc?.hideLoader() } if let error = error { completion(.failure(error)) return } if let data = data { do { let response = try JSONDecoder().decode(T.self, from: data) completion(.success(response)) } catch { completion(.failure(error)) } } else { let error = NSError(domain: "YourDomain", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data received"]) completion(.failure(error)) } } task.resume() } }
ViewController调用代码
func postGenericCall2(){ let param = ["name": "john", "job": "AAA" ] let requestObject = RequestObjectAll( params: param, method: .post, urlPath: "https://reqres.in/api/users", isTokenNeeded: false, isLoaderNeed: true, vc: self ) APINetworkManagerAll.sharedInstance.serviceCall(requestObject: requestObject) { (result: Result<PostGenModelSmall, Error>) in switch result { case .success(let response): print("result of post service call.....: \(response)") case .failure(let error): print("Error decoding JSON: \(error)") } } } struct PostGenModelSmall: Codable { let name: String let job: String let id: String let createdAt: String }
报错信息
Error decoding JSON: keyNotFound(CodingKeys(stringValue: "name", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: "name", intValue: nil) ("name").", underlyingError: nil))
补充说明
直接编写postServiceCall()方法发起请求可获取与Postman一致的响应,因此问题出在APINetworkManagerAll的serviceCall方法中。直接请求代码如下:
func postServiceCall(){ let params: [String : Any] = ["name": "john", "job": "AAA"] var request = URLRequest(url: URL(string: "https://reqres.in/api/users")!) request.httpMethod = "POST" request.httpBody = try? JSONSerialization.data(withJSONObject: params, options: []) request.addValue("application/json", forHTTPHeaderField: "Content-Type") DispatchQueue.global().async { let session = URLSession.shared let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in print(response!) do { guard let data = data, error == nil else{ print(error?.localizedDescription ?? "No data") return } let json = try JSONSerialization.jsonObject(with: data) as? [String : Any] print("post data with response....\(json)") } catch { print("error") } }) task.resume() } }
输出结果:
post data with response....Optional(["id": 582, "createdAt": 2024-03-12T06:28:51.904Z, "job": AAA, "name": john])
核心问题
对比直接请求的代码,APINetworkManagerAll的serviceCall方法缺少Content-Type请求头设置,服务器无法识别请求体为JSON格式,导致参数未被正确解析,返回的响应不符合预期,最终触发解码错误。
此外,当requestObject.params为nil时,代码使用["": ""]作为默认值,会向服务器发送无效JSON,建议改为空字典[:]更合理。
修复后的代码
修改APINetworkManagerAll的serviceCall方法:
func serviceCall<T: Decodable>(requestObject: RequestObjectAll, completion: @escaping (Result<T, Error>) -> Void) { if requestObject.isLoaderNeed { requestObject.vc?.showLoader() } guard let url = URL(string: requestObject.urlPath) else { if requestObject.isLoaderNeed { requestObject.vc?.hideLoader() } completion(.failure(NetworkError.invalidURL)) return } var urlRequest = URLRequest(url: url) urlRequest.httpMethod = requestObject.method.rawValue // 添加Content-Type请求头,告知服务器请求体为JSON格式 urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type") // 优化默认参数处理,使用空字典替代无效的["": ""] guard let httpBody = try? JSONSerialization.data(withJSONObject: requestObject.params ?? [:], options: []) else { if requestObject.isLoaderNeed { requestObject.vc?.hideLoader() } completion(.failure(NetworkError.invalidParameters)) return } urlRequest.httpBody = httpBody let task = URLSession.shared.dataTask(with: urlRequest) { data, _, error in if requestObject.isLoaderNeed { requestObject.vc?.hideLoader() } if let error = error { completion(.failure(error)) return } if let data = data { do { let response = try JSONDecoder().decode(T.self, from: data) completion(.success(response)) } catch { completion(.failure(error)) } } else { let error = NSError(domain: "YourDomain", code: 0, userInfo: [NSLocalizedDescriptionKey: "No data received"]) completion(.failure(error)) } } task.resume() }
同时,确保NetworkError枚举包含invalidParameters(如果尚未定义):
enum NetworkError: Error { case invalidURL case invalidParameters // 其他自定义错误类型 }
内容的提问来源于stack exchange,提问作者Swift

