[英]Swift Codable struct with a generic property
假设我们有一个基于 cursor 的分页 API,其中可以对多个端点进行分页。 这种端点的响应总是如下:
{
"nextCursor": "someString",
"PAYLOAD_KEY": <generic response>
}
因此有效载荷总是返回 cursor 并且有效载荷密钥取决于我们使用的实际端点。 例如,如果我们有GET /users
,它可能是users
,键的值是对象数组,或者我们可以调用GET /some-large-object
,键是item
,有效负载是 object。
最重要的是,响应始终是 object 和 cursor 以及另一个键及其关联值。
试图在 Swift 中使它通用,我在想这个:
public struct Paginable<Body>: Codable where Body: Codable {
public let body: Body
public let cursor: String?
private enum CodingKeys: String, CodingKey {
case body, cursor
}
}
现在这段代码的唯一问题是它期望Body
可以在"body"
键下访问,但事实并非如此。
我们可以有一个struct User: Codable
和 paginable specialized as Paginable<[Users]>
其中 API 响应 object 将具有数组的关键users
。
我的问题是如何使这个通用Paginable
结构起作用,以便我可以从Body
类型指定 JSON 有效负载键?
我能想到的最简单的解决方案是让解码后的Body
给你解码密钥:
protocol PaginableBody: Codable {
static var decodingKey: String { get }
}
struct RawCodingKey: CodingKey, Equatable {
let stringValue: String
let intValue: Int?
init(stringValue: String) {
self.stringValue = stringValue
intValue = nil
}
init(intValue: Int) {
stringValue = "\(intValue)"
self.intValue = intValue
}
}
struct Paginable<Body: PaginableBody>: Codable {
public let body: Body
public let cursor: String?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RawCodingKey.self)
body = try container.decode(Body.self, forKey: RawCodingKey(stringValue: Body.decodingKey))
cursor = try container.decodeIfPresent(String.self, forKey: RawCodingKey(stringValue: "nextCursor"))
}
}
例如:
let jsonString = """
{
"nextCursor": "someString",
"PAYLOAD_KEY": {}
}
"""
let jsonData = Data(jsonString.utf8)
struct SomeBody: PaginableBody {
static let decodingKey = "PAYLOAD_KEY"
}
let decoder = JSONDecoder()
let decoded = try? decoder.decode(Paginable<SomeBody>.self, from: jsonData)
print(decoded)
另一种选择是始终将“其他”非光标键作为正文:
struct Paginable<Body: Codable>: Codable {
public let body: Body
public let cursor: String?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RawCodingKey.self)
let cursorKey = RawCodingKey(stringValue: "nextCursor")
cursor = try container.decodeIfPresent(String.self, forKey: cursorKey)
// ! should be replaced with proper decoding error thrown
let bodyKey = container.allKeys.first { $0 != cursorKey }!
body = try container.decode(Body.self, forKey: bodyKey)
}
}
另一种可能的选择是将解码密钥直接传递给userInfo
中的JSONDecoder
,然后在init(from:)
中访问它。 这将为您提供最大的灵活性,但您必须在解码期间始终指定它。
例如,您可以将泛型 model 与类型擦除一起使用
struct GenericInfo: Encodable {
init<T: Encodable>(name: String, params: T) {
valueEncoder = {
var container = $0.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: . name)
try container.encode(params, forKey: .params)
}
}
// MARK: Public
func encode(to encoder: Encoder) throws {
try valueEncoder(encoder)
}
// MARK: Internal
enum CodingKeys: String, CodingKey {
case name
case params
}
let valueEncoder: (Encoder) throws -> Void
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.