Я пытаюсь декодировать JSON, получаемый с сервера. Поскольку это приложение пишется на SwiftUI, я подумал, что могу также попробовать Combine. Я использовал .decode () как часть моей цепочки комбайнов, которая работала хорошо, но теперь мне нужно декодировать json, который с этим не работает.

Я пытаюсь декодировать JSON формата в структуру Team. Однако проблема в том, что это не гарантируется на сервере, в этих случаях сервер просто не возвращает JSON (однако у него все еще есть правильный код ответа HTTPS, поэтому я знаю, когда это так). У меня вопрос, как декодировать полученные данные в необязательный Team? (где это либо декодированные командные данные, либо ноль, если JSON не получен.

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()
    }
0
Harry Day 21 Фев 2021 в 23:34

1 ответ

Лучший ответ

В идеале вы можете проверить ответ сервера и решить, что вы хотите сделать, скажем, с учетом определенного 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()
   }
}
2
New Dev 22 Фев 2021 в 16:43