解码自定义init中的所有属性(枚举类的所有属性并为其分配值)

时间:2018-10-15 14:27:22

标签: ios swift codable decoder

我目前正在开发一个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
}

谢谢

2 个答案:

答案 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代码。这样一来,当您不再需要它时,可以很容易地将其拔出。