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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 11:52:28