如果首次解码失败,则使用Combine和Swift解码另一个响应 [英] Decode another response if first decoding failed using Combine and Swift

查看:56
本文介绍了如果首次解码失败,则使用Combine和Swift解码另一个响应的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下模型:

struct Response: Decodable {
    let message: String
}

struct ErrorResponse: Decodable {
    let errorMessage: String
}

enum APIError: Error {
    case network(code: Int, description: String)
    case decoding(description: String)
    case api(description: String)
}

我正在尝试使用以下流程获取网址并解析JSON响应:

I'm trying to fetch an url and parse the JSON response using this flow:

func fetch(url: URL) -> AnyPublisher<Response, APIError> {
    URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))

        // #1 URLRequest fails, throw APIError.network
        .mapError { .network(code: $0.code.rawValue, description: $0.localizedDescription) }

        // #2 try to decode data as a `Response`
        .tryMap { JSONDecoder().decode(Response.self, from: $0.data) }

        // #3 if decoding fails, decode as an `ErrorResponse`
        //    and throw `APIError.api(description: errorResponse.errorMessage)`

        // #4 if both fail, throw APIError.decoding
        
        // #5 return
        .eraseToAnyPublisher()
}

我对#3 有问题:如何在 tryMap 部分之后解码原始数据?

I have a problem with #3: how can I decode the original data after the tryMap part?

似乎唯一可以访问的值是来自 tryMap 的错误,但是我需要原始数据来解码 ErrorRepsonse .

It seems like the only value I can access there is the error coming from tryMap but I need the original data to decode an ErrorRepsonse.

注意:不幸的是,错误响应带有200状态,区分它们的唯一方法是对它们进行解码.

Note: unfortunately the error response comes with the 200 status and the only way to differentiate them is to decode them.

推荐答案

您可以使用 flatMap 并对其内部进行解码:

You can use a flatMap and deal with decoding inside it:

URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))
   // #1 URLRequest fails, throw APIError.network
   .mapError { 
       APIError.network(code: $0.code.rawValue, description: $0.localizedDescription) 
   }

   .flatMap { data -> AnyPublisher<Response, APIError> in
      // #2 try to decode data as a `Response`
      if let response = try? JSONDecoder().decode(Response.self, from: data) {
         return Just(response).setFailureType(to: APIError.self)
                    .eraseToAnyPublisher()
      }

      do {
         // #3 if decoding fails, decode as an `ErrorResponse`
         let error = try decoder.decode(ErrorResponse.self, from: data)
             
         // and throw `APIError.api(description: errorResponse.errorMessage)`
         return Fail(error: APIError.api(description: errorResponse.errorMessage))
                    .eraseToAnyPublisher()
      } catch {
         // #4 if both fail, throw APIError.decoding
         return Fail(error: APIError.decoding(description: error.localizedDescription))
                    .eraseToAnyPublisher()
      }
   }


编辑

如果您想以纯"方式执行此操作,结合方式,那么您仍然想要使用 flatMap 来访问原始数据并避开原始可能的网络错误,然后使用 tryCatch 来处理失败路径.

If you want to do this in a "pure" Combine way, then you'd still want to use a flatMap to have access the original data and to sidestep the original possible network error, and then use tryCatch to deal with the failure path.

请注意,步骤4位于步骤3的两个部分之间:

Note that step #4 comes between two parts of step #3:

URLSession.shared.dataTaskPublisher(for: URLRequest(url: url))
   // #1 URLRequest fails, throw APIError.network
   .mapError { 
       APIError.network(code: $0.code.rawValue, description: $0.localizedDescription) 
   }
   .flatMap { v in
      Just(v)

         // #2 try to decode data as a `Response`
         .decode(type: Response.self, decoder: JSONDecoder())

         // #3 if decoding fails,
         .tryCatch { _ in
            Just(v)
               // #3.1 ... decode as an `ErrorResponse`
               .decode(type: ErrorResponse.self, decoder: JSONDecoder())
               
               // #4 if both fail, throw APIError.decoding
               .mapError { _ in APIError.decoding(description: "error decoding") }

               // #3.2 ... and throw `APIError.api
               .tryMap { throw APIError.api(description: $0.errorMessage) }
         }

         // force unwrap is not terrible here, since you know 
         // that `tryCatch` only ever throws APIError
         .mapError { $0 as! APIError }
   }

这篇关于如果首次解码失败,则使用Combine和Swift解码另一个响应的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆