Swift 合并 decodeIfPresent 版本?

Swift Combine version of decodeIfPresent?

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

我正在尝试将格式的 JSON 解码为 Team 结构。然而,问题是这不能保证在服务器上存在,在那些情况下,服务器只是 returns 没有 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 只会给您数据 - 所以,这里没什么可玩的。

你可以尝试解码,如果失败,return nil:

return retrieveData(with: request)
   .flatMap {
      Just([=10=])
         .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 { [=11=] 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()
   }
}