对单个属性使用多个 CodingKeys

Jon*_*nas 5 swift

是否可以为单个属性使用多个 CodingKeys?

struct Foo: Decodable {

    enum CodingKeys: String, CodingKey {
        case contentIDs = "contentIds" || "Ids" || "IDs" // something like this?
    }

    let contentIDs: [UUID]
}
Run Code Online (Sandbox Code Playgroud)

小智 10

您可以通过使用多个 CodingKey 枚举和自定义初始值设定项来做到这一点。让我通过一个例子来展示它

enum CodingKeys: String, CodingKey {
    case name = "prettyName"
}

enum AnotherCodingKeys: String, CodingKey {
    case name
}

init(from decoder: Decoder) throws {
    let condition = true // or false
    if condition {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
    } else {
        let values = try decoder.container(keyedBy: AnotherCodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
    }
}
Run Code Online (Sandbox Code Playgroud)


PGD*_*Dev 6

实现init(from:)初始化程序并根据您的要求添加自定义解析,即

struct Foo: Decodable {
    let contentIDs: [String]

    enum CodingKeys: String, CodingKey, CaseIterable {
        case contentIds, Ids, IDs
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        if let key = container.allKeys.filter({ CodingKeys.allCases.contains($0) }).first, let ids = try container.decodeIfPresent([String].self, forKey: key) {
            self.contentIDs = ids
        } else {
            self.contentIDs = []
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


San*_*bra 6

更好、更简洁的解决方案可能是在 KeyedDecodingContainer 上创建一个可以重用的扩展。

    extension KeyedDecodingContainer{
        enum ParsingError:Error{
          case noKeyFound
          /*
            Add other errors here for more use cases
          */
        }

        func decode<T>(_ type:T.Type, forKeys keys:[K]) throws -> T where T:Decodable {
           for key in keys{
             if let val = try? self.decode(type, forKey: key){
               return val
             }
           }
         throw ParsingError.noKeyFound
       }
    }
    
Run Code Online (Sandbox Code Playgroud)

上述函数可以如下使用:

struct Foo:Decodable{
     let name:String
     let contentIDs: [String]

     enum CodingKeys:String,CodingKey {
         case name
         case contentIds, Ids, IDs
     }

    init(from decoder:Decoder) throws{
        let container = try decoder.container(keyedBy:CodingKeys.self)
        contentIDs = try container.decode([String].self, forKeys:[.contentIds,.IDs,.Ids])
        name = try container.decode(String.self, forKey: .name)
    }
}
Run Code Online (Sandbox Code Playgroud)


Rob*_*ier 5

You can't do literally what you're describing, but you can make the process quite mechanical, and you can turn this into autogenerated code using Sourcery if you need that.

首先,像往常一样,您需要一个AnyKey(有一天我希望将其添加到 stdlib;甚至 Apple 文档也引用它......)

struct AnyKey: CodingKey {
    var stringValue: String
    var intValue: Int?
    init(stringValue: String) {
        self.stringValue = stringValue
    }

    init?(intValue: Int) {
        self.stringValue = String(intValue)
        self.intValue = intValue
    }
}
Run Code Online (Sandbox Code Playgroud)

然后您需要一个可以从可能的键列表中进行解码的新方法。这个特定的实现尝试字典中的元素,然后回退到键的名称。(这样,如果他们只有自己的名字,则不必将其放入字典中。)

extension KeyedDecodingContainer where K == AnyKey {
    func decode<T>(_ type: T.Type, forMappedKey key: String, in keyMap: [String: [String]]) throws -> T where T : Decodable{

        for key in keyMap[key] ?? [] {
            if let value = try? decode(T.self, forKey: AnyKey(stringValue: key)) { 
                return value
            }
        }

        return try decode(T.self, forKey: AnyKey(stringValue: key))
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,乏味但简单(如果您愿意的话,可以生成代码)初始化:

init(from decoder: Decoder) throws {
    let keyMap = [
        "contentIDs": ["contentIds", "Ids", "IDs"],
        "title": ["name"],
    ]

    let container = try decoder.container(keyedBy: AnyKey.self)

    self.contentIDs = try container.decode([UUID].self, forMappedKey: "contentIDs", in: keyMap)
    self.title = try container.decode(String.self, forMappedKey: "title", in: keyMap)
    self.count = try container.decode(Int.self, forMappedKey: "count", in: keyMap)
}
Run Code Online (Sandbox Code Playgroud)

您可以使用本地函数使其更加整洁:

init(from decoder: Decoder) throws {
    let keyMap = [
        "contentIDs": ["contentIds", "Ids", "IDs"],
        "title": ["name"],
    ]

    let container = try decoder.container(keyedBy: AnyKey.self)

    func decode<Value>(_ key: String) throws -> Value where Value: Decodable {
        return try container.decode(Value.self, forMappedKey: key, in: keyMap)
    }

    self.contentIDs = try decode("contentIDs")
    self.title = try decode("title")
    self.count = try decode("count")
    // ...
}
Run Code Online (Sandbox Code Playgroud)

然而,我认为使用 Decodable 不会比这更简单,因为你无法解码动态类型,并且 Swift 需要确保初始化所有属性。(这使得创建for循环来进行初始化变得非常困难。)