Swift Combine 版本的 decodeIfPresent?

问题描述

我正在尝试对从服务器接收到的 JSON 进行一些解码。因为这个应用程序是用 SwiftUI 编写的,所以我想我也可以试试结合。我一直在使用 .decode() 作为我的组合链的一部分,它一直运行良好,但现在我需要解码 json ,这将无法使用。

我正在尝试将格式的 JSON 解码为 Team 结构。然而,问题是这不能保证存在于服务器上,在这些情况下,服务器只是不返回 JSON(但是它仍然具有正确的 HTTPS 响应代码,所以我知道什么时候是这种情况)。我的问题是如何将接收到的数据解码为可选的 Team?(如果没有收到 JSON,则它是解码的团队数据或 nil。

struct Team: Codable,Identifiable,Hashable {
    var id: UUID
    var name: String
    var currentrating: Int

    enum CodingKeys: String,CodingKey {
        case id = "id"
        case name = "name"
        case currentrating = "rating"
    }
}
func fetch<T: Decodable>(
        from endpoint: Endpoint,with decoder: JSONDecoder = JSONDecoder()
    ) -> AnyPublisher<T,DatabaseError> {
        // Get the URL from the endpoint
        guard let url = endpoint.url else { ... }
        
        let request = URLRequest(url: url)
        
        // Get the publisher data from the server
        // retrieveData is a function with the return type AnyPublisher<Data,DatabaseError>
        return retrieveData(with: request)
            // Try to decode into a decodable object
            .decode(type: T.self,decoder: decoder)
            // If there is an error,map it to a DatabaseError
            .mapError { ... }
            // Main thread
            .receive(on: dispatchQueue.main)
            // Type erase
            .erasetoAnyPublisher()
    }

解决方法

理想情况下,您可以检查服务器响应并决定您想要做什么,比如给定一个特定的 HTTP 代码或者如果 data 为空,但在您的情况下,retrieveData 只是给您数据 -所以,这里没什么可玩的。

您可以尝试解码,如果失败,则返回 nil

return retrieveData(with: request)
   .flatMap {
      Just($0)
         .decode(type: T?.self,decoder: decoder)
         .replaceError(with: nil)
   }
   .mapError { ... }
   //... etc

上面的缺点是它会隐藏任何实际的解码错误,比如类型不匹配,所以你可以更精确地处理并且只在数据不为空时解码。

这是一个可能的 decodeIfPresent 实现:

extension Publisher where Output == Data {
   func decodeIfPresent<T: Decodable,Coder: TopLevelDecoder>(
            type: T.Type,decoder: Coder
            ) -> AnyPublisher<T?,Error> where Coder.Input == Output {

      self.mapError { $0 as Error }
         .flatMap { d -> AnyPublisher<T?,Error> in
            if d.isEmpty {
               return Just<T?>(nil)
                  .setFailureType(to: Error.self)
                  .eraseToAnyPublisher()
            } else {
               return Just(d)
                  .decode(type: T?.self,decoder: decoder)
                  .eraseToAnyPublisher()
            }
         }
         .eraseToAnyPublisher()
   }
}