Swift中能否合并JSONSerialization与JSONDecoder实现统一API请求函数?
合并Swift API请求函数,同时支持Codable解码与原始JSON字典返回
我在Swift中实现了两个API请求函数:一个支持用Codable结构体解码返回数据,另一个返回未解码的[String: Any]类型JSON字典,用于通过Socket.io向服务端发送数据。现在想把这两个函数合并为一个,同时保留两种使用场景的能力。
原调用示例
场景1:解码为Codable结构体
let url = "https://remote-production.up.railway.app/getRoomId" let apiService = APIService(urlString: url) Task.detached { do { let response: RoomId = try await apiService.getJSON() } }
场景2:获取未解码JSON用于Socket.io发送
Task { do { let responseData: [String: Any] = try await apiService.getSerializationJSON() let dataToSend: [String: Any] = ["requestId": requestId, "response": responseData] /// 向服务端客户端发送事件 self.socket.emit("host_response", dataToSend) self.userSettings.recentConnections.append(userIp) // 将userIp添加到recentConnections列表 } catch { print("API请求出错:\(error.localizedDescription)") } }
原有APIService.swift代码
import Foundation class APIService { let urlString: String let headersDict: [String: String] init(urlString: String, headersDict: [String: String] = [:]) { self.urlString = urlString self.headersDict = headersDict } func getSerializationJSON() async throws -> [String: Any] { guard let url = URL(string: urlString) else { throw APIError.invalidURL } // 设置请求对象:示例头、方法等 var urlRequest = URLRequest(url: url) // 如果有自定义头则设置 headersDict.forEach { key, value in urlRequest.setValue(value, forHTTPHeaderField: key) } do { let (data, response) = try await URLSession.shared.data(for: urlRequest) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw APIError.invalidResponseStatus } do { let decodedDictionary = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] guard let jsonDictionary = decodedDictionary else { throw APIError.corruptData } return jsonDictionary } catch { throw APIError.decodingError(error.localizedDescription) } } catch { throw APIError.dataTaskError(error.localizedDescription) } } func getJSON<T: Decodable>(dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) async throws -> T { guard let url = URL(string: urlString) else { throw APIError.invalidURL } do { let (data, response) = try await URLSession.shared.data(from: url) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw APIError.invalidResponseStatus } let decoder = JSONDecoder() decoder.dateDecodingStrategy = dateDecodingStrategy decoder.keyDecodingStrategy = keyDecodingStrategy do { let decodedData = try decoder.decode(T.self, from: data) return decodedData } catch { throw APIError.decodingError(error.localizedDescription) } } catch { throw APIError.dataTaskError(error.localizedDescription) } } } enum APIError: Error, LocalizedError { case invalidURL case invalidResponseStatus case dataTaskError(String) case corruptData case decodingError(String) var errorDescription: String? { switch self { case .invalidURL: return NSLocalizedString("端点URL无效", comment: "") case .invalidResponseStatus: return NSLocalizedString("API未能返回有效响应。", comment: "") case .dataTaskError(let string): return string case .corruptData: return NSLocalizedString("提供的数据似乎已损坏", comment: "") case .decodingError(let string): return string } } }
合并后的实现方案
你可以抽离重复的网络请求逻辑为私有方法,用一个泛型函数作为统一入口,根据返回类型自动选择解码方式,同时兼容两种使用场景。
修改后的APIService代码
import Foundation class APIService { let urlString: String let headersDict: [String: String] init(urlString: String, headersDict: [String: String] = [:]) { self.urlString = urlString self.headersDict = headersDict } // 统一请求入口,支持返回Codable类型或[String: Any]字典 func request<T>(dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate, keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy = .useDefaultKeys) async throws -> T { // 先获取网络返回的原始数据 let data = try await fetchRawData() // 根据返回类型T选择对应的解析方式 if T.self == [String: Any].self { guard let jsonDict = try JSONSerialization.jsonObject(with: data) as? T else { throw APIError.corruptData } return jsonDict } else if let decodableType = T.self as? Decodable.Type { let decoder = JSONDecoder() decoder.dateDecodingStrategy = dateDecodingStrategy decoder.keyDecodingStrategy = keyDecodingStrategy guard let decodedResult = try decoder.decode(decodableType, from: data) as? T else { throw APIError.decodingError("无法解析为目标类型") } return decodedResult } else { throw APIError.decodingError("不支持的返回类型") } } // 私有方法:封装重复的网络请求逻辑 private func fetchRawData() async throws -> Data { guard let url = URL(string: urlString) else { throw APIError.invalidURL } var urlRequest = URLRequest(url: url) headersDict.forEach { key, value in urlRequest.setValue(value, forHTTPHeaderField: key) } let (data, response) = try await URLSession.shared.data(for: urlRequest) guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { throw APIError.invalidResponseStatus } return data } } enum APIError: Error, LocalizedError { case invalidURL case invalidResponseStatus case dataTaskError(String) case corruptData case decodingError(String) var errorDescription: String? { switch self { case .invalidURL: return NSLocalizedString("端点URL无效", comment: "") case .invalidResponseStatus: return NSLocalizedString("API未能返回有效响应。", comment: "") case .dataTaskError(let string): return string case .corruptData: return NSLocalizedString("提供的数据似乎已损坏", comment: "") case .decodingError(let string): return string } } }
合并后的调用方式
场景1:解码为Codable结构体
let url = "https://remote-production.up.railway.app/getRoomId" let apiService = APIService(urlString: url) Task.detached { do { let response: RoomId = try await apiService.request() // 处理解码后的RoomId实例 } catch { print(error.localizedDescription) } }
场景2:获取未解码JSON用于Socket.io发送
Task { do { let responseData: [String: Any] = try await apiService.request() let dataToSend: [String: Any] = ["requestId": requestId, "response": responseData] self.socket.emit("host_response", dataToSend) self.userSettings.recentConnections.append(userIp) } catch { print("API请求出错:\(error.localizedDescription)") } }
实现说明
- 代码复用:将URL校验、请求头设置、网络请求、状态码校验这些重复逻辑封装到
fetchRawData私有方法中,减少冗余代码。 - 泛型统一入口:
request<T>方法通过判断返回类型T,自动切换解析方式:- 当
T是[String: Any]时,使用JSONSerialization解析原始JSON - 当
T遵循Decodable时,使用JSONDecoder解码为目标结构体
- 当
- 兼容原有习惯:调用方式和之前几乎一致,仅需将原方法名替换为
request,无需额外学习成本。
内容的提问来源于stack exchange,提问作者Pastor J.G Seigle
相关产品推荐
相关产品推荐

