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

Swift泛型网络请求函数解码空响应数据失败的解决方法

问题

我编写了一个带泛型结果的网络请求函数fetch,代码如下:

private func fetch<Request, Response>(_ endpoint: Types.EndPoint,
                                      method: HTTPMethod = .get,
                                      body: Request? = nil,
                                      then callback: ((Result<Response, Types.Error>) -> Void)? = nil
    ) where Request: Encodable, Response: Decodable {

    //Network request creation...

    let dataTask = URLSession.shared
            .dataTask(with: urlRequest) { data, response, error in
                if let error = error {
                    print("Fetch error: \(error)")
                    callback?(.failure(.generic(reason: "Could not fetch data :\(error.localizedDescription)")))
                } else {
                                            
                    if let data = data {


                        do {
                            let result = try self.decoder.decode(Response.self, from: data) // **exception here**
                            callback?(.success(result))
                        } catch {
                            print("Decoding error: \(error)")
                            callback?(.failure(.generic(reason: "Could not decode data :\(error.localizedDescription)")))
                        }

                    }
                }
    }
}


......

apiClient.fetch(.updateUser, API.Types.Request.User) { (result: Result<API.Types.Response.Empty, API.Types.Error>) in
        
        //handle result
        
}

其中API.Types.Response.Empty是一个遵循Decodable协议的空结构体:

struct Empty: Decodable {}

调用该接口时,服务器成功返回状态码200,但响应内容为空(Content-Length为0),此时JSON解码抛出异常:

Unable to parse empty data." UserInfo={NSDebugDescription=Unable to parse empty data

请问该如何正确处理这种空响应数据的解码场景?

解决方案

方法一:给Empty结构体自定义解码逻辑

直接修改Empty结构体,让它兼容空数据的解码场景,不需要改动网络请求代码:

struct Empty: Decodable {
    init(from decoder: Decoder) throws {
        do {
            // 尝试解码空容器,空结构体本身没有编码键,不会报错
            let _ = try decoder.container(keyedBy: CodingKeys.self)
        } catch {
            // 捕获空数据的解码错误,直接初始化实例
            if let decodingError = error as? DecodingError,
               case .dataCorrupted(let context) = decodingError,
               context.debugDescription.contains("Unable to parse empty data") {
                return
            }
            // 其他错误正常抛出
            throw error
        }
    }
    
    // 定义空的编码键枚举,避免默认解码逻辑报错
    private enum CodingKeys: String, CodingKey {}
}

这种方式把逻辑封装在Empty结构体内部,其他地方调用时不需要额外判断。

方法二:在fetch函数中提前处理空数据

在解码前先检查数据是否为空,如果是空数据且目标类型是Empty,直接返回成功实例:

private func fetch<Request, Response>(_ endpoint: Types.EndPoint,
                                      method: HTTPMethod = .get,
                                      body: Request? = nil,
                                      then callback: ((Result<Response, Types.Error>) -> Void)? = nil
    ) where Request: Encodable, Response: Decodable {

    //Network request creation...

    let dataTask = URLSession.shared
            .dataTask(with: urlRequest) { data, response, error in
                if let error = error {
                    print("Fetch error: \(error)")
                    callback?(.failure(.generic(reason: "Could not fetch data :\(error.localizedDescription)")))
                } else {
                    if let data = data {
                        // 空数据且目标类型是Empty时直接返回成功
                        if data.isEmpty, Response.self is Empty.Type {
                            callback?(.success(Empty() as! Response))
                            return
                        }
                        
                        do {
                            let result = try self.decoder.decode(Response.self, from: data)
                            callback?(.success(result))
                        } catch {
                            print("Decoding error: \(error)")
                            callback?(.failure(.generic(reason: "Could not decode data :\(error.localizedDescription)")))
                        }
                    } else {
                        // 数据为nil的情况,同样判断是否是Empty类型
                        if Response.self is Empty.Type {
                            callback?(.success(Empty() as! Response))
                        } else {
                            callback?(.failure(.generic(reason: "Empty data received but expected non-empty response")))
                        }
                    }
                }
    }
}

这种方式把判断逻辑集中在网络请求层,适合不想修改Empty结构体的场景。

方法三:扩展JSONDecoder支持空数据解码

给JSONDecoder添加一个扩展方法,统一处理空数据的解码逻辑:

extension JSONDecoder {
    func decodeHandlingEmpty<T: Decodable>(_ type: T.Type, from data: Data) throws -> T {
        guard !data.isEmpty else {
            // 如果是Empty类型,返回实例;否则抛出自定义错误
            guard let emptyType = T.self as? Empty.Type else {
                throw DecodingError.dataCorrupted(
                    DecodingError.Context(
                        codingPath: [],
                        debugDescription: "Empty data received but expected non-empty response"
                    )
                )
            }
            return emptyType.init() as! T
        }
        return try decode(type, from: data)
    }
}

然后在fetch函数中替换原来的解码代码:

do {
    let result = try self.decoder.decodeHandlingEmpty(Response.self, from: data)
    callback?(.success(result))
} catch {
    print("Decoding error: \(error)")
    callback?(.failure(.generic(reason: "Could not decode data :\(error.localizedDescription)")))
}

这种方式的好处是可以把空数据处理逻辑复用在其他需要的地方。

内容的提问来源于stack exchange,提问作者Sachinthana Aluvihare

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 21:37:33