如何让Xcode的URLSession接收NestJS API的任意响应?
问题:Xcode中如何接收NestJS API的任意响应(无需硬编码结构)
我有一个向NestJS API发送HTTP POST请求的Swift文件,网站端能正常接收API返回的任意响应,但在Xcode里必须硬编码响应结构。我原本以为Xcode只支持JSON响应,现在想确认能不能让Xcode接收API返回的任意响应,并寻求解决方案。
HttpPOST.swift
import Foundation func apiCall(url: String, body: Dictionary<String, AnyHashable>, completion: @escaping (Response) -> Void){ guard let url = URL(string: "http://192.168.1.71:3000/user/login")else{ return } var request = URLRequest(url: url) // method, body, headers request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") let body: [String: AnyHashable] = body request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed) // make rqs let task = URLSession.shared.dataTask(with: request){ data, response, error in guard let data = data,error == nil else { return } do { let response = try JSONDecoder().decode(Response.self, from: data) completion(response) } catch { print(error) } } task.resume() } struct Response: Codable { var usernameError: String?; var passwordError: String?; var logOk: Bool?; var token: String?; }
响应示例
Response(usernameError: Optional("enteruname"), passwordError: Optional("enterpasswd"), logOk: Optional(false), token: nil)
解决方案
首先明确:Xcode(准确说是Swift的URLSession)完全支持接收任意格式的响应,不止JSON。你之前的代码只是强制把响应解码成了固定的Response结构,所以才只能处理符合该结构的JSON。以下是几种不同场景的解决方案:
方案1:接收任意JSON响应(无需硬编码结构)
如果API返回的还是JSON,但结构不固定,可以把响应解码成[String: Any]或者Any类型,摆脱固定结构的限制:
修改后的apiCall函数:
import Foundation func apiCall(url: String, body: [String: AnyHashable], completion: @escaping (Any?, Error?) -> Void) { guard let url = URL(string: "http://192.168.1.71:3000/user/login") else { completion(nil, NSError(domain: "InvalidURL", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的URL"])) return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed) let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else { completion(nil, error) return } do { let jsonResponse = try JSONSerialization.jsonObject(with: data, options: .allowFragments) completion(jsonResponse, nil) } catch { completion(nil, error) } } task.resume() }
使用示例:
apiCall(url: "", body: ["username": "test", "password": "123"]) { response, error in if let json = response as? [String: Any] { let usernameError = json["usernameError"] as? String let logOk = json["logOk"] as? Bool let token = json["token"] as? String // 根据实际返回字段按需处理 } }
方案2:接收任意格式的响应(非JSON)
如果API可能返回非JSON格式(比如纯文本、XML等),可以直接处理原始Data,或者转换成字符串:
修改后的apiCall函数:
import Foundation func apiCall(url: String, body: [String: AnyHashable], completion: @escaping (Data?, Error?) -> Void) { guard let url = URL(string: "http://192.168.1.71:3000/user/login") else { completion(nil, NSError(domain: "InvalidURL", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的URL"])) return } var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed) let task = URLSession.shared.dataTask(with: request) { data, response, error in completion(data, error) } task.resume() }
转换成字符串处理的示例:
apiCall(url: "", body: ["username": "test", "password": "123"]) { data, error in if let data = data, let responseString = String(data: data, encoding: .utf8) { print("原始响应内容:\(responseString)") // 根据响应格式(比如XML、纯文本)自行解析 } }
方案3:保留类型安全同时支持灵活响应
如果既想保留类型安全,又要处理不同结构的响应,可以用枚举+自定义解码的方式,覆盖API可能返回的所有情况:
定义响应枚举:
enum APIResponse: Codable { case loginSuccess(token: String) case loginError(usernameError: String?, passwordError: String?) init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) let logOk = try container.decodeIfPresent(Bool.self, forKey: .logOk) ?? false if logOk, let token = try container.decodeIfPresent(String.self, forKey: .token) { self = .loginSuccess(token: token) } else { let usernameError = try container.decodeIfPresent(String.self, forKey: .usernameError) let passwordError = try container.decodeIfPresent(String.self, forKey: .passwordError) self = .loginError(usernameError: usernameError, passwordError: passwordError) } } enum CodingKeys: String, CodingKey { case logOk, token, usernameError, passwordError } func encode(to encoder: Encoder) throws { // 若无需编码请求数据可忽略此方法 } }
修改apiCall的completion类型和解码逻辑:
func apiCall(url: String, body: [String: AnyHashable], completion: @escaping (APIResponse?, Error?) -> Void) { // 前面的请求构建逻辑不变 let task = URLSession.shared.dataTask(with: request) { data, response, error in guard let data = data, error == nil else { completion(nil, error) return } do { let response = try JSONDecoder().decode(APIResponse.self, from: data) completion(response, nil) } catch { completion(nil, error) } } task.resume() }
使用示例:
apiCall(url: "", body: ["username": "test", "password": "123"]) { response, error in guard let response = response else { return } switch response { case .loginSuccess(let token): print("登录成功,Token:\(token)") case .loginError(let usernameErr, let passwordErr): print("登录失败:用户名错误\(usernameErr ?? ""),密码错误\(passwordErr ?? "")") } }
内容的提问来源于stack exchange,提问作者Alms
相关产品推荐
相关产品推荐

