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

Swift中Post泛型Codable API响应异常问题排查

问题:Post请求参数传递失败,响应解码报错

我创建了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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 01:55:27