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

Ios 解码自定义init中的所有属性(枚举类的所有属性并为其赋值),ios,swift,codable,decoder,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的变化如下:

我目前正在做一个项目,它的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
}
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
    }
}
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
        }
    }
}
所以我在解码器和nil对象中得到了一些类型不匹配错误。为了防止解码器使整个对象失败,我实现了一个自定义init,并为任何未知属性设置了nil,其工作原理与charm NOTE类似:我将在稍后处理这些更改,这仅适用于计划外和临时更改:

但对于更大的类和结构,我必须手动编写任何属性,这需要时间,更重要的是,有时我会错过一个要设置的属性

那么,有没有办法枚举所有属性并设置它们? 我知道我可以从容器中获取所有密钥,但不知道如何设置其相应的属性:

for key in container.allKeys {
   self.<#corresponding_property#> = (try? container.decodeIfPresent(<#corresponding_type#>.self, forKey: key)) ?? nil
}

谢谢

在您的特定示例中,问题是您的值的类型不断变化:有时标记是字符串,有时是整数。你需要的不仅仅是你的可选方法;这关系到某件东西是否存在,而不是它是否具有正确的类型。您需要一个可以解码并表示字符串或整数的联合类型,如下所示:

{
    "tag": "ANIMAL",
    "name": "Dog",
    "type": 1
}
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
    }
}
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
        }
    }
}
利用这一点,我能够用一个单一的动物结构类型来解码您的两个示例:

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类型,该类型可以清除键,而不是多个属性,您的动物将有一个单独的属性,即字典,如下所示:

{
    "tag": "ANIMAL",
    "name": "Dog",
    "type": 1
}
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
    }
}
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功能将属性façade放在字典前面。但这是留给读者的练习

您需要的工具是。它是Swift的元编程包装器,可以为您编写样板文件,因为您知道要编写什么,所以Sourcery的理想用途就是它的冗长。关于Sourcery最重要的一点是,不管它的名字如何,它都没有魔力。它只是基于其他Swift代码为您编写Swift代码。这样,当你不再需要它时,就可以很容易地把它拔出来

Codable的全部要点是提供一种类型安全和静态的方式,用于将Swift类型编码到JSON或从JSON解码Swift类型。使用Swift/Codable时,您要查找的动态代码是不可能的。此外,您应该实际修复这些问题,而不是通过这个定制init来掩盖客户端数据模型和API之间的不匹配。此外,使用API响应设计工具,自动生成生成API请求/响应的代码(如Swagger),可以避免出现此类错误。问题是,值的类型不断变化:有时标记是字符串,有时是整数。你需要的不仅仅是你的可选方法;这关系到某件东西是否存在,而不是它是否具有正确的类型。您将需要一个StringOrInteger类型,正如这里所解释的:或者,正如已经建议的那样,完全放弃可解码,因为如果API像这样跳舞是不合适的。是的,我知道,但是当API没有文档并且正在开发时,我们不希望在调试模式下出现这么多崩溃。实际上我把初始化器放在里面如果调试。。。endif@DávidPásztorIf您无法控制API,要么等待其开发完成,要么在其实现波动很大时使用模拟服务器。另一方面,如果您可以控制API设计,请让它的开发人员使用一种更适合解决此类问题的工具,如前所述。另外,在开发过程中捕获崩溃这样的bug总比使用您的方法掩盖它们并让bug意外泄漏到生产中要好。谢谢@DávidPászor。但问题在于枚举所有属性并为它们赋值,而不是这个用例。我更新了问题的标题以便更清楚。谢谢@matt,但正如我所说的,我不想处理每一种情况,这只是演示其中一种情况的案例,不会永远改变。我还没有完成。等等,好了,谢谢你花时间解释。但正如你所说,这与我最初的要求相反。dynamicMemberLookup还用于访问结构(如字典)的成员,该结构是类型安全的,并带有点符号。我问的
是一个自动流程,用于减少示例初始值设定项中的重复代码,使用类似枚举的工具。谢谢实际上,我在寻找一些纯粹的swift代码。Sourcery是一种分析代码并为我编写重复代码的脚本。关键是根本不写它们!干的事件不会自动干燥或编写脚本。我不理解此评论。预编译器的输出与DRY有什么关系?我不明白你根本不写是什么意思!您的意思是说,由于Swift编译器在专门处理泛型函数时通常会复制大量代码,所以禁止使用泛型函数吗?这是一个非常奇怪的要求。DRY只是一个模糊的指导原则,比如最小化内存流失或者更喜欢O1算法。这本身不是一个目标。但是,正如所述,在Swift中没有解决您的问题的方法。不过,Sourcery可以解决it繁琐的问题。