我正在使用Decodable
协议来解析从外部源接收的JSON。在解码了我知道的属性之后,JSON中可能还有一些未知且尚未解码的属性。例如,如果外部源在未来的某个时间点向JSON添加了一个新属性,我想通过将它们存储在[String: Any]
字典(或替代)中来保留这些未知属性,因此值不会被忽略了。
问题在于,在解码了我所知道的属性之后,容器上没有任何访问器来检索尚未解码的属性。我知道decoder.unkeyedContainer()
可以用来迭代每个值但是这在我的情况下不起作用,因为为了使它工作,你需要知道你迭代的值类型但是值JSON中的类型并不总是相同的。
这是我在游乐场中为我想要实现的目标的一个例子:
// Playground
import Foundation
let jsonData = """
{
"name": "Foo",
"age": 21
}
""".data(using: .utf8)!
struct Person: Decodable {
enum CodingKeys: CodingKey {
case name
}
let name: String
let unknownAttributes: [String: Any]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
// I would like to store the `age` attribute in this dictionary
// but it would not be known at the time this code was written.
self.unknownAttributes = [:]
}
}
let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: jsonData)
// The `person.unknownAttributes` dictionary should
// contain the "age" attribute with a value of 21.
我希望unknownAttributes
字典在这种情况下存储age
属性和值以及任何其他可能的值类型,如果它们将来从外部源添加到JSON中。
我想要做这样的事情的原因是我可以坚持JSON中存在的未知属性,以便在将来更新代码时,我将能够在知道属性键后适当地处理它们。
我已经在StackOverflow和Google上进行了大量搜索,但还没有遇到过这个独特的案例。提前谢谢!
答案 0 :(得分:5)
你们不断提出新的方法来强调Swift 4编码API ......;)
支持所有值类型的通用解决方案可能无法实现。但是,对于原始类型,您可以尝试:
使用基于字符串的键创建简单的CodingKey
类型:
struct UnknownCodingKey: CodingKey {
init?(stringValue: String) { self.stringValue = stringValue }
let stringValue: String
init?(intValue: Int) { return nil }
var intValue: Int? { return nil }
}
然后使用上面KeyedDecodingContainer
键入的标准UnknownCodingKey
编写一般解码函数:
func decodeUnknownKeys(from decoder: Decoder, with knownKeys: Set<String>) throws -> [String: Any] {
let container = try decoder.container(keyedBy: UnknownCodingKey.self)
var unknownKeyValues = [String: Any]()
for key in container.allKeys {
guard !knownKeys.contains(key.stringValue) else { continue }
func decodeUnknownValue<T: Decodable>(_ type: T.Type) -> Bool {
guard let value = try? container.decode(type, forKey: key) else {
return false
}
unknownKeyValues[key.stringValue] = value
return true
}
if decodeUnknownValue(String.self) { continue }
if decodeUnknownValue(Int.self) { continue }
if decodeUnknownValue(Double.self) { continue }
// ...
}
return unknownKeyValues
}
最后,使用decodeUnknownKeys
函数填充unknownAttributes
词典:
struct Person: Decodable {
enum CodingKeys: CodingKey {
case name
}
let name: String
let unknownAttributes: [String: Any]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
let knownKeys = Set(container.allKeys.map { $0.stringValue })
self.unknownAttributes = try decodeUnknownKeys(from: decoder, with: knownKeys)
}
}
一个简单的测试:
let jsonData = """
{
"name": "Foo",
"age": 21,
"token": "ABC",
"rate": 1.234
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let person = try! decoder.decode(Person.self, from: jsonData)
print(person.name)
print(person.unknownAttributes)
打印:
富
[“年龄”:21,“令牌”:“ABC”,“费率”:1.234]