Swift 可使用以编程方式提供的编码密钥进行解码

Swift decodable with programatically provided coding keys

这是从JSON解码的简化模型:

struct Info: Decodable {
    var text: String
    var num: Int
}

struct Root: Decodable {
    let info: Info
}

有时我只需要解码 Info.textInfo.num,但有时两者都需要解码,并且为了支持所有选项,我制作了类似的解码结构,例如:

// For text only
struct InfoText: Decodable {
    var text: String
}

struct RootText: Decodable {
    let info: InfoText
}

// For num only
struct InfoNum: Decodable {
    var num: Int
}

struct RootNum: Decodable {
    let info: InfoNum
}

这种方法会产生大量克隆代码和运行时检查来处理结构,因此是否可以仅使用单个结构解码提供的编码密钥?

可以使用 userInfo 属性 向解码器提供任何上下文信息,在这种情况下,我们可以传递一组编码密钥并在解码过程中使用此信息:

struct Info: Decodable {
    var text: String?
    var num: Int?
    
    static var keys = CodingUserInfoKey(rawValue: "keys")!
    
    enum CodingKeys: String, CodingKey {
        case text, num
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        
        guard let keys = decoder.userInfo[Self.keys] as? [CodingKeys] else {
            return
        }
        
        if keys.contains(.text) {
            text = try container.decode(String.self, forKey: .text)
        }
        
        if keys.contains(.num) {
            num = try container.decode(Int.self, forKey: .num)
        }
    }
}


struct Root: Decodable {
    let info: Info
}

let json = #"{ "info" : { "text": "Hello", "num": 20 } }"#.data(using: .utf8)!

let decoder = JSONDecoder()
let keys: [Info.CodingKeys] = [.text]
decoder.userInfo[Info.keys] = keys
let root = try decoder.decode(Root.self, from: json)
print(root)

// Outputs:
Root(info: Info(text: Optional("Hello"), num: nil))