【发布时间】:2019-11-08 15:00:58
【问题描述】:
我会尽量以最好的方式解释我想要做什么,就像我在过去几天谷歌搜索时所做的那样。
我的应用程序正在与几个不同的 API 进行通信,但让我们首先考虑来自一个 API 的响应。每个端点的响应包含一些“公共参数”,例如状态或错误消息,以及我们最感兴趣的单个对象或对象数组,它带来重要数据,我们可能想要对其进行编码、存储、放置它到 Realm、CoreData 等。
例如,单个对象的响应:
{
"status": "success",
"response_code": 200,
"messages": [
"message1",
"message2"
]
"data": {
OBJECT we're interested in.
}
}
或者,使用对象数组响应:
{
"status": "success",
"response_code": 200,
"messages": [
"message1",
"message2"
]
"data": [
{
OBJECT we're interested in.
},
{
OBJECT we're interested in.
}
]
}
好的。这很简单,很容易理解。
现在,我想编写一个“根”对象,它将包含“通用参数”或 status、response_code 和 messages,并为特定对象(或对象数组)提供另一个属性我们感兴趣的。
继承
第一种方法是创建一个根对象,如下所示:
class Root: Codable {
let status: String
let response_code: Int
let messages: [String]?
private enum CodingKeys: String, CodingKey {
case status, response_code, messages
}
required public init(from decoder: Decoder) throws {
let container = try? decoder.container(keyedBy: CodingKeys.self)
status = try container?.decodeIfPresent(String.self, forKey: .code) ?? ""
response_code = try container?.decodeIfPresent(Int.self, forKey: .error) ?? 0
messages = try container?.decodeIfPresent([String].self, forKey: .key)
}
public func encode(to encoder: Encoder) throws {}
}
一旦我有了这个根对象,我就可以创建从这个根对象继承的特定对象,并在 JSONDecoder 中传递我的特定对象,在那里,我有一个很好的解决方案。但是,此解决方案不适用于数组。 也许对于某些人来说不是,但我不能强调我不想制作额外的“复数”对象,该对象仅存在以容纳对象数组,例如:
class Objects: Root {
let objects: [Object]
// Code that decodes array of "Object" from "data" key
}
struct Object: Codable {
let property1
let property2
let property3
// Code that decodes all properties of Object
}
它看起来不干净,它需要单独的对象来简单地保存一个数组,它在某些情况下由于继承而产生存储到 Realm 的问题,它最重要的是产生了可读性较差的代码。
泛型
我的第二个想法是尝试使用泛型,所以我创建了a little something like this:
struct Root<T: Codable>: Codable {
let status: String
let response_code: Int
let messages: [String]?
let data: T?
private enum CodingKeys: String, CodingKey {
case status, response_code, messages, data
}
required public init(from decoder: Decoder) throws {
let container = try? decoder.container(keyedBy: CodingKeys.self)
status = try container?.decodeIfPresent(String.self, forKey: .code) ?? ""
response_code = try container?.decodeIfPresent(Int.self, forKey: .error) ?? 0
messages = try container?.decodeIfPresent([String].self, forKey: .key)
data = try container.decodeIfPresent(T.self, forKey: .data)
}
public func encode(to encoder: Encoder) throws {}
}
有了这个,我可以像这样将单个对象和对象数组传递给 JSONDecoder:
let decodedValue = try JSONDecoder().decode(Root<Object>.self, from: data)
// or
let decodedValue = try JSONDecoder().decode(Root<[Object]>.self, from: data)
这很不错。我可以在 Root 结构的 .data 属性中获取我需要的结构,并按照我喜欢的方式使用它,作为单个对象或作为对象数组。我可以很容易地存储它,但是我想要不受限制地进行操作,继承在上面的例子中带来。
这个想法在我的案例中失败的地方是当我想在某个不确定 T 设置为什么的地方访问“公共属性”时。
这是对我的应用程序中实际发生的事情的简化解释,我将对其进行一些扩展以解释此通用解决方案对我不起作用的地方,最后问我的问题。
问题和疑问
如顶部所述,应用程序使用 3 个 API,所有 3 个 API 都有不同的 Root 结构,当然还有很多不同的“子结构” - 命名它们。我在应用程序中有一个单独的位置,单个 APIResponse 对象,该对象可以返回到应用程序的 UI 部分,在其中我从 decoded value 提取 1 个可读错误,decoded value 是那个“子结构”,是我的任何一个“特定对象”,Car,Dog,House,Phone。
使用 继承 解决方案,我能够做这样的事情:
struct APIResponse <T> {
var value: T? {
didSet {
extractErrorDescription()
}
}
var errorDescription: String? = "Oops."
func extractErrorDescription() {
if let responseValue = value as? Root1, let error = responseValue.errors.first {
self.errorDescription = error
}
else if let responseValue = value as? Root2 {
self.errorDescription = responseValue.error
}
else if let responseValue = value as? Root3 {
self.errorDescription = responseValue.message
}
}
}
但使用 Generics 解决方案,我无法做到这一点。如果我尝试使用 Root1 或 Root2 或 Root3 编写相同的代码,如 Generics 示例所示:
func extractErrorDescription() {
if let responseValue = value as? Root1, let error = responseValue.errors.first {
self.errorDescription = error
}
}
我会收到错误消息Generic parameter 'T' could not be inferred in cast to 'Root1',在这里,我试图提取错误,我不知道哪个子结构被传递给了 Root1。是Root1<Dog> 或Root1<Phone> 还是Root1<Car> - 我不知道如何弄清楚,我显然需要知道值是Root1 还是Root2 或Root3。
我正在寻找的解决方案是一种可以让我区分 Root 对象和上面显示的泛型解决方案的解决方案,或者一种允许我以某种完全不同的方式进行架构解码的解决方案,记住一切我写的,尤其是避免“复数”对象的能力
*如果 JSON 没有通过 JSON 验证器,请忽略,它是为了这个问题而手写的
**如果编写的代码无法运行,请忽略,这更像是一个架构问题,而不是如何编译一些代码。
【问题讨论】:
-
你不需要像
if let responseValue = value as? Root1<T>这样它知道要转换的类型吗? -
对于继承路由,您可以使每个路由都符合一个通用协议并在每个路由上都有一个计算属性以返回错误消息
-
@Scriptable 在您的第一条评论中是的,我需要
if let responseValue = value as? Root1<T>,但我不知道T在那个地方是什么,可以是与@ 一起使用的20 或30 个结构中的任何一个987654355@。如果我检查每一个,1 会起作用,我通常会访问我的“公共属性”,但这并不是真正的方法。 -
你找到解决办法了吗?
标签: ios swift generics codable jsondecoder