是否可以对一个属性使用多个CodingKeys?
struct Foo: Decodable {
enum CodingKeys: String, CodingKey {
case contentIDs = "contentIds" || "Ids" || "IDs" // something like this?
}
let contentIDs: [UUID]
}
答案 0 :(得分:2)
实施 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 = []
}
}
}
答案 1 :(得分:2)
您不能按字面意思进行操作,但是可以使过程非常机械化,并且可以根据需要使用Sourcery将其转换为自动生成的代码。
首先,与往常一样,您需要一个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
}
}
然后,您需要一种可以从可能的密钥列表中解码的新方法。这个特定的实现尝试使用字典中的元素,然后退回到键的名称。 (这样,如果它们只有自己的名称,则不必在字典中放入内容。)
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))
}
}
最后,乏味但简单(如果愿意,可以生成代码):
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)
}
您可以使用本地函数使其更加整洁:
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")
// ...
}
但是,我认为使用Decodable不会比这简单得多,因为您无法解码动态类型,而Swift需要确保初始化所有属性。 (这使得创建for
循环进行初始化非常困难。)
答案 2 :(得分:1)
您不能做您想做的事。从问题和您以后的评论中,您似乎发现了一些非常糟糕的JSON。可腐烂不是为这种事情而做的。使用JSONSerialization并随后清理混乱。
答案 3 :(得分:1)
您可以通过使用多个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)
}
}
答案 4 :(得分:1)
一种更好,更清洁的解决方案是在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
}
}
以上功能可以按如下方式使用:
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)
}
}