Swift通用JSON解码器问题:API响应解码类型不匹配报错求助
解决API响应解码类型不匹配问题
问题背景
API始终返回如下结构的JSON响应:
{ "status": "string", "error": "string", "meta": {}, "data": "任意类型(数组、对象、布尔值等)" }
尝试用如下结构体解码:
struct ApiResponseModel: Decodable { let status: String let error: String let data: Data let meta: Data enum CodingKeys: CodingKey { case status, error, data, meta } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.status = try container.decode(String.self, forKey: .status) self.error = try container.decode(String.self, forKey: .error) self.data = try container.decode(Data.self, forKey: .data) self.meta = try container.decode(Data.self, forKey: .meta) } }
使用代码:
let serializedBody = try! JSONSerialization.data(withJSONObject: [ "email": email, "password": password ], options: [.sortedKeys, .prettyPrinted]) let response = try await AsyncNetworkManager.makeRequest( endpoint: ApiDict.loginsV1, httpMethod: .post, data: serializedBody ) let responseModelData = try JSONDecoder().decode(ApiResponseModel.self, from: response) return try JSONDecoder().decode(LoginModel.self, from: responseModelData.data)
运行时报错:
typeMismatch(Foundation.Data, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil)], debugDescription: "Expected to decode Data but found a dictionary instead.", underlyingError: nil)) The data couldn’t be read because it isn’t in the correct format.
报错原因
JSONDecoder默认将Data类型解析为Base64编码的字符串,但你的API返回的data字段是JSON对象/数组,并非Base64字符串,因此类型不匹配导致解码失败。
解决方案
方案1:使用泛型结构体直接解码目标模型(推荐)
定义泛型响应结构体,直接将data解码为目标模型,避免二次解码:
struct ApiResponseModel<T: Decodable>: Decodable { let status: String let error: String let data: T let meta: [String: Any] // 若meta有固定结构,可替换为对应的Decodable结构体 enum CodingKeys: CodingKey { case status, error, data, meta } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) status = try container.decode(String.self, forKey: .status) error = try container.decode(String.self, forKey: .error) data = try container.decode(T.self, forKey: .data) // 处理任意结构的meta字段 let metaRaw = try container.decode(Data.self, forKey: .meta) meta = try JSONSerialization.jsonObject(with: metaRaw, options: []) as? [String: Any] ?? [:] } }
使用方式:
let serializedBody = try! JSONSerialization.data(withJSONObject: [ "email": email, "password": password ], options: [.sortedKeys, .prettyPrinted]) let response = try await AsyncNetworkManager.makeRequest( endpoint: ApiDict.loginsV1, httpMethod: .post, data: serializedBody ) // 直接解码为包含LoginModel的响应模型 let responseModel = try JSONDecoder().decode(ApiResponseModel<LoginModel>.self, from: response) return responseModel.data
方案2:保留二次解码,将JSON字段转为Data
如果必须保留二次解码的逻辑,需要先将data和meta对应的JSON值转为Data,需借助AnyCodable处理任意JSON类型:
// 处理任意JSON类型的Codable包装器 struct AnyCodable: Codable { let value: Any init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let int = try? container.decode(Int.self) { value = int } else if let string = try? container.decode(String.self) { value = string } else if let bool = try? container.decode(Bool.self) { value = bool } else if let double = try? container.decode(Double.self) { value = double } else if let array = try? container.decode([AnyCodable].self) { value = array.map { $0.value } } else if let dict = try? container.decode([String: AnyCodable].self) { value = dict.mapValues { $0.value } } else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "不支持的JSON类型") } } func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch value { case let int as Int: try container.encode(int) case let string as String: try container.encode(string) case let bool as Bool: try container.encode(bool) case let double as Double: try container.encode(double) case let array as [Any]: try container.encode(array.map(AnyCodable.init)) case let dict as [String: Any]: try container.encode(dict.mapValues(AnyCodable.init)) default: throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: encoder.codingPath, debugDescription: "不支持的JSON类型")) } } } // 修改后的响应模型 struct ApiResponseModel: Decodable { let status: String let error: String let data: Data let meta: Data enum CodingKeys: CodingKey { case status, error, data, meta } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) status = try container.decode(String.self, forKey: .status) error = try container.decode(String.self, forKey: .error) // 将JSON值转为Data let dataValue = try container.decode(AnyCodable.self, forKey: .data) data = try JSONEncoder().encode(dataValue) let metaValue = try container.decode(AnyCodable.self, forKey: .meta) meta = try JSONEncoder().encode(metaValue) } }
使用方式与原代码一致,即可正常解码。
内容的提问来源于stack exchange,提问作者breq
相关产品推荐
相关产品推荐

