首页 > 解决方案 > 解码通用可解码类型

问题描述

我需要创建一个通用结构来保存从网络返回的任何可解码类型,所以我创建了这样的东西:

struct NetworkResponse<Wrapped: Decodable>: Decodable {
    var result: Wrapped
}

所以我可以使用这样的解码方法:

struct MyModel: Decodable {
  var id: Int
  var name: String
  var details: String
}

func getData<R: Decodable>(url: URL) -> AnyPublisher<R, Error>
    URLSession.shared
   .dataTaskPublisher(for: url)
   .map(\.data)
   .decode(type: NetworkResponse<R>.self, decoder: decoder)
   .map(\.result)
   .eraseToAnyPublisher()

//call site
let url = URL(string: "https://my/Api/Url")!
let models: [MyModel] = getData(url: url)
  .sink {
   //handle value here
}

但是,我注意到来自网络的一些响应包含result密钥,而另一些则不包含:

结果:

{
"result": { [ "id": 2, "name": "some name", "details": "some details"] }
}

没有结果:

[ "id": 2, "name": "some name", "details": "some details" ]

这会导致发布者出现以下错误,因为它在返回的 json 中.map(\.result)找不到密钥:result

(typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil)))

我如何处理NetworkResponse结构中的任何一种情况以避免此类错误?

标签: swiftcodablecombinedecodable

解决方案


您发布的 JSON 无效,但我假设这是一个错字,实际上是:

{ "id": 2, "name": "some name", "details": "some details" }
// or
{ "result": { "id": 2, "name": "some name", "details": "some details" } }

{ }而不是[ ]


可能最干净的是使用手动解码器,如果第一种类型失败,它可以回退到另一种类型:

struct NetworkResponse<Wrapped> {
    let result: Wrapped
}

extension NetworkResponse: Decodable where Wrapped: Decodable {
    private struct ResultResponse: Decodable {
        let result: Wrapped
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.singleValueContainer()
        do {
            let result = try container.decode(ResultResponse.self)
            self.result = result.result
        } catch DecodingError.keyNotFound, DecodingError.typeMismatch {
            self.result = try container.decode(Wrapped.self)
        }
    }
}

或者,您可以退回到 Combine。我不会采用这种方法,但为了完整起见:

URLSession.shared
   .dataTaskPublisher(for: url)
   .map(\.data)
   .flatMap { data in
       Just(data)
         .decode(type: NetworkResponse<R>.self, decoder: decoder)
         .map(\.result)
         .catch { _ in
             Just(data)
                .decode(type: R.self, decoder: decoder)
         }
   }
   .eraseToAnyPublisher()

推荐阅读