Swift 4中使用Codable处理含AnyObject的基实体类问题
处理Swift 4中Codable解析动态类型的通用响应
这种通用API响应的场景太常见了——先返回基础状态信息,再根据状态返回不同类型的数据。我给你提供两种实用的方案,你可以根据自己的需求选择:
方案一:分两步解析(推荐,简单直观)
核心思路是先解析出status和statusDescription,再根据状态值动态解析records对应的具体类型:
- 定义基础状态结构体:先只解析状态相关字段
struct BaseStatusResponse: Codable { let status: Int? let statusDescription: String? }
- 分阶段解析JSON数据
// 假设你拿到的API返回数据是data: Data do { // 第一步:解析基础状态信息 let baseResponse = try JSONDecoder().decode(BaseStatusResponse.self, from: data) // 初始化你的基类实体 let finalResponse = entStatusAndDescription() finalResponse.status = baseResponse.status finalResponse.statusDescription = baseResponse.statusDescription // 第二步:根据status动态解析records guard let status = baseResponse.status else { // 处理状态为空的情况 return } switch status { case 200: // 解析成客户实体(假设你的客户实体是Customer,已经实现Codable) let fullResponse = try JSONDecoder().decode(Response<Customer>.self, from: data) finalResponse.records = fullResponse.records as AnyObject case 201: // 解析成订单实体(示例) let fullResponse = try JSONDecoder().decode(Response<Order>.self, from: data) finalResponse.records = fullResponse.records as AnyObject default: // 处理其他状态,比如records为空或其他类型 finalResponse.records = nil } // 现在finalResponse就是你需要的完整实体了 } catch { // 处理解析错误 print("解析失败:\(error.localizedDescription)") } // 配合使用的泛型响应结构体 struct Response<T: Codable>: Codable { let status: Int? let statusDescription: String? let records: T? }
方案二:自定义Codable实现(适合需要基类直接支持Codable的场景)
如果你希望entStatusAndDescription本身直接实现Codable,可以通过自定义解码逻辑,先保存records的原始JSON数据,之后再转成具体类型:
public class entStatusAndDescription: NSObject, Codable { var status: Int? var statusDescription: String? var records: AnyObject? // 这里保存原始JSON数据或解析后的实体 enum CodingKeys: String, CodingKey { case status, statusDescription, records } required public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) // 解析基础状态字段 status = try container.decodeIfPresent(Int.self, forKey: .status) statusDescription = try container.decodeIfPresent(String.self, forKey: .statusDescription) // 保存records的原始JSON数据(后续可转成具体类型) if let recordsRaw = try container.decodeIfPresent(AnyCodable.self, forKey: .records) { records = recordsRaw.value as AnyObject } } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(status, forKey: .status) try container.encodeIfPresent(statusDescription, forKey: .statusDescription) // 编码records字段 if let recordsObj = records { try container.encode(AnyCodable(recordsObj), forKey: .records) } } } // 自定义AnyCodable类型,用于兼容任意JSON数据 struct AnyCodable: Codable { let value: Any init(_ value: Any) { self.value = value } init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let intVal = try? container.decode(Int.self) { value = intVal } else if let stringVal = try? container.decode(String.self) { value = stringVal } else if let boolVal = try? container.decode(Bool.self) { value = boolVal } else if let doubleVal = try? container.decode(Double.self) { value = doubleVal } else if let arrayVal = try? container.decode([AnyCodable].self) { value = arrayVal.map { $0.value } } else if let dictVal = try? container.decode([String: AnyCodable].self) { value = dictVal.mapValues { $0.value } } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "无法解析成Any类型") } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch value { case let intVal as Int: try container.encode(intVal) case let stringVal as String: try container.encode(stringVal) case let boolVal as Bool: try container.encode(boolVal) case let doubleVal as Double: try container.encode(doubleVal) case let arrayVal as [Any]: try container.encode(arrayVal.map(AnyCodable.init)) case let dictVal as [String: Any]: try container.encode(dictVal.mapValues(AnyCodable.init)) default: throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: container.codingPath, debugDescription: "无法编码该类型")) } } }
使用这种方案时,你可以在解析完entStatusAndDescription后,根据status把records转成具体实体:
if let status = finalResponse.status, status == 200, let recordsDict = finalResponse.records as? [String: Any] { let recordsData = try JSONSerialization.data(withJSONObject: recordsDict, options: []) let customer = try JSONDecoder().decode(Customer.self, from: recordsData) // 现在customer就是解析后的客户实体了 }
小提示
- 如果不需要Objective-C兼容,建议把
entStatusAndDescription改成Swift的struct,更轻量且符合Swift的设计习惯。 - 方案一中的泛型
Response结构体可以复用在所有API请求中,非常方便。
内容的提问来源于stack exchange,提问作者Shravan Agrawal
相关产品推荐
相关产品推荐

