繁体   English   中英

Swift 具有通用属性的 Codable 结构

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM