我目前正在开发一个API尚未准备好的项目。因此某些属性的类型有时会更改。例如,我有这个结构:
struct Animal: Codable {
var tag: Int?
var name: String?
var type: String?
var birthday: Date?
}
此json:
{
"tag": 12,
"name": "Dog",
"type": "TYPE1"
}
但是在开发中,json更改为以下内容:
{
"tag": "ANIMAL",
"name": "Dog",
"type": 1
}
所以我在解码器和无对象中遇到了类型不匹配错误。为了防止解码器使整个对象失败,我实现了一个自定义 init 并为任何未知属性设置 nil ,它的工作方式类似于charm(注意:我将处理这些问题以后进行更改,这仅适用于计划外和临时的更改):
#if DEBUG
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
tag = (try? container.decodeIfPresent(Int.self, forKey: .tag)) ?? nil
name = (try? container.decodeIfPresent(String.self, forKey: .name)) ?? nil
type = (try? container.decodeIfPresent(String.self, forKey: .type)) ?? nil
birthday = (try? container.decodeIfPresent(Date.self, forKey: .birthday)) ?? nil
}
#endif
但是对于较大的类和结构,我必须手动编写任何属性,这需要时间,更重要的是,有时我错过了要设置的属性!
那么有什么方法可以枚举所有属性并进行设置? 我知道我可以从容器中获取所有密钥,但是不知道如何设置它的对应属性:
for key in container.allKeys {
self.<#corresponding_property#> = (try? container.decodeIfPresent(<#corresponding_type#>.self, forKey: key)) ?? nil
}
谢谢
答案 0 :(得分:1)
您的特定示例中的问题是值的 type 不断变化:有时tag
是一个字符串,有时是一个整数。您将需要的比可选方法要多得多。处理某个事物是否存在,而不是它是否具有正确的类型。您需要一个可以解码并表示字符串或整数的联合类型,如下所示:
enum Sint : Decodable {
case string(String)
case int(Int)
enum Err : Error { case oops }
init(from decoder: Decoder) throws {
let con = try decoder.singleValueContainer()
if let s = try? con.decode(String.self) {
self = .string(s)
return
}
if let i = try? con.decode(Int.self) {
self = .int(i)
return
}
throw Err.oops
}
}
使用该代码,我可以使用单个Animal结构类型解码您的两个示例:
struct Animal: Decodable {
var tag: Sint
var name: String
var type: Sint
}
let j1 = """
{
"tag": 12,
"name": "Dog",
"type": "TYPE1"
}
"""
let j2 = """
{
"tag": "ANIMAL",
"name": "Dog",
"type": 1
}
"""
let d1 = j1.data(using: .utf8)!
let a1 = try! JSONDecoder().decode(Animal.self, from: d1)
let d2 = j2.data(using: .utf8)!
let a2 = try! JSONDecoder().decode(Animal.self, from: d2)
好的,但是现在让我们说您甚至都不知道键是什么。然后,您需要一个AnyCodingKey类型,无论它们是什么,该类型都可以清除它们,并且Animal具有多个属性,而不是多个属性,例如Dictionary:
struct Animal: Decodable {
var d = [String : Sint]()
struct AnyCodingKey : CodingKey {
var stringValue: String
var intValue: Int?
init(_ codingKey: CodingKey) {
self.stringValue = codingKey.stringValue
self.intValue = codingKey.intValue
}
init(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
let con = try decoder.container(keyedBy: AnyCodingKey.self)
for key in con.allKeys {
let result = try con.decode(Sint.self, forKey: key)
self.d[key.stringValue] = result
}
}
}
因此,现在您可以使用完全未知的密钥解码某些内容,这些密钥的值可以是字符串或整数。同样,这对于您提供的JSON示例也可以正常工作。
请注意,这与您最初要求执行的操作相反。我没有使用struct属性名来生成密钥,而是简单地接受了任何一种类型的密钥,并通过使用字典将其灵活地存储在struct中。您还可以使用新的Swift 4.2 dynamicMemberLookup
功能在该字典的前面放置一个属性外观。但这留给读者练习!
答案 1 :(得分:0)
您需要的工具是Sourcery。这是Swift的元编程包装程序,它将为您编写样板文件,因为您知道要编写的内容,所以这很乏味(这就是Sourcery的理想之选)。关于Sourcery的重要一点是(尽管有名称),没有魔术。它只是基于其他Swift代码为您编写Swift代码。这样一来,当您不再需要它时,可以很容易地将其拔出。