Swift:挂接到 Decodable.init() 以获取未指定的密钥?
Swift: hook into Decodable.init() for an unspecified key?
我有一些 JSON 我想用 JSONDecoder
解码。问题是,其中一个属性的名称在从服务器发送时是动态的。
像这样:
{
"someRandomName": [ [1,2,3], [4,5,6] ],
"staticName": 12345
}
如果 someRandomName
在构建时未知,我该如何解码?我一直在浏览 www 寻找答案,但仍然没有快乐。无法真正理解 Decodable
、CodingKey
的工作原理。有的例子长达几十行,好像不太对!
编辑 我应该指出密钥在运行时是已知的,所以也许我可以在解码对象时传入它?
是否有任何方法可以连接到协议方法或属性之一以启用此解码?我不介意是否必须为此对象编写定制解码器:所有其他 JSON 都很好且标准。
编辑
好的,我的理解已经到此为止了:
struct Pair: Decodable {
var pair: [[Double]]
var last: Int
private struct CodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
// Use for integer-keyed dictionary
var intValue: Int?
init?(intValue: Int) {
// We are not using this, thus just return nil
return nil
}
}
init(from decoder: Decoder) throws {
// just to stop the compiler moaning
pair = [[]]
last = 0
let container = try decoder.container(keyedBy: CodingKeys.self)
// how do I generate the key for the correspond "pair" property here?
for key in container.allKeys {
last = try container.decode(Int.self, forKey: CodingKeys(stringValue: "last")!)
pair = try container.decode([[Double]].self, forKey: CodingKeys(stringValue: key.stringValue)!)
}
}
}
init() {
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
let jsonData = Data(jsonString.utf8)
// this gives: "Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "last", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a number instead.", underlyingError: nil))"
let decodedResult = try! JSONDecoder().decode(Pair.self, from: jsonData)
dump(decodedResult)
}
所以我现在明白 CodingKey
一致性正在为序列化数据生成密钥,而不是 Swift 结构(现在我想起来这很有道理)。
那么我现在如何即时生成 pair
的案例,而不是像这样硬编码呢?我知道它与我需要实现的 init(from decoder: Decoder)
有关,但对于我来说,我无法弄清楚它是如何工作的。请帮忙!
编辑 2
好的,我现在很接近了。解码似乎适用于此:
struct Pair: Decodable {
var pair: [[Double]]
var last: Int
private enum CodingKeys : String, CodingKey {
case last
}
private struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
// Use for integer-keyed dictionary
var intValue: Int?
init?(intValue: Int) {
// We are not using this, thus just return nil
return nil
}
}
init(from decoder: Decoder) throws {
// just to stop the compiler moaning
pair = [[]]
last = 0
let container1 = try decoder.container(keyedBy: CodingKeys.self)
last = try container1.decode(Int.self, forKey: .last)
let container2 = try decoder.container(keyedBy: DynamicCodingKeys.self)
for key in container2.allKeys {
pair = try container2.decode([[Double]].self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
}
}
}
这段代码似乎完成了它的工作:检查函数本身的 last
和 pair
属性,看起来不错;但我在尝试解码时遇到错误:
init() {
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
let jsonData = Data(jsonString.utf8)
// Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [DynamicCodingKeys(stringValue: "last", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a number instead."
let decodedResult = try! JSONDecoder().decode(Pair.self, from: jsonData)
dump(decodedResult)
}
我离得很近我能尝到它的味道...
你要找的是 JSONSerializer
而不是 JSONDecoder
我猜是 https://developer.apple.com/documentation/foundation/jsonserialization.
因为密钥不可预测,所以最好转换成Dictionary
。或者你可以看看这个https://swiftsenpai.com/swift/decode-dynamic-keys-json/
如果动态密钥在运行时已知,您可以通过解码器的 userInfo
字典传递它。
首先创建两个扩展
extension CodingUserInfoKey {
static let dynamicKey = CodingUserInfoKey(rawValue: "dynamicKey")!
}
extension JSONDecoder {
convenience init(dynamicKey: String) {
self.init()
self.userInfo[.dynamicKey] = dynamicKey
}
}
在结构中实现 CodingKeys
作为能够动态创建键的结构。
struct Pair : Decodable {
let last : Int
let pair : [[Double]]
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
static let last = CodingKeys(stringValue: "last")!
static func makeKey(name: String) -> CodingKeys {
return CodingKeys(stringValue: name)!
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let dynamicKey = decoder.userInfo[.dynamicKey] as? String else {
throw DecodingError.dataCorruptedError(forKey: .makeKey(name: "pair"), in: container, debugDescription: "Dynamic key in userInfo is missing")
}
last = try container.decode(Int.self, forKey: .last)
pair = try container.decode([[Double]].self, forKey: .makeKey(name: dynamicKey))
}
}
现在创建 JSONDecoder
传递已知的动态名称
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
do {
let decoder = JSONDecoder(dynamicKey: "XBTUSD")
let result = try decoder.decode(Pair.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}
编辑:
如果 JSON 始终只包含两个键,这是一种更简单的方法:
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
}
}
struct Pair : Decodable {
let last : Int
let pair : [[Double]]
}
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
let lastPath = codingPath.last!
if lastPath.stringValue == "last" { return lastPath }
return AnyKey(stringValue: "pair")!
})
let result = try decoder.decode(Pair.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}
我现在有了一些实际有效的代码!
struct Pair: Decodable {
var pair: [[Double]]
var last: Int
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
static let last = CodingKeys(stringValue: "last")!
static func makeKey(name: String) -> CodingKeys {
return CodingKeys(stringValue: name)!
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
last = try container.decode(Int.self, forKey: .last)
let key = container.allKeys.first(where: { [=10=].stringValue != "last" } )?.stringValue
pair = try container.decode([[Double]].self, forKey: .makeKey(name: key!))
}
}
init() {
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
let jsonData = Data(jsonString.utf8)
// Ask JSONDecoder to decode the JSON data as DecodedArray
let decodedResult = try! JSONDecoder().decode(Pair.self, from: jsonData)
dump(decodedResult)
}
我有一些 JSON 我想用 JSONDecoder
解码。问题是,其中一个属性的名称在从服务器发送时是动态的。
像这样:
{
"someRandomName": [ [1,2,3], [4,5,6] ],
"staticName": 12345
}
如果 someRandomName
在构建时未知,我该如何解码?我一直在浏览 www 寻找答案,但仍然没有快乐。无法真正理解 Decodable
、CodingKey
的工作原理。有的例子长达几十行,好像不太对!
编辑 我应该指出密钥在运行时是已知的,所以也许我可以在解码对象时传入它?
是否有任何方法可以连接到协议方法或属性之一以启用此解码?我不介意是否必须为此对象编写定制解码器:所有其他 JSON 都很好且标准。
编辑
好的,我的理解已经到此为止了:
struct Pair: Decodable {
var pair: [[Double]]
var last: Int
private struct CodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
// Use for integer-keyed dictionary
var intValue: Int?
init?(intValue: Int) {
// We are not using this, thus just return nil
return nil
}
}
init(from decoder: Decoder) throws {
// just to stop the compiler moaning
pair = [[]]
last = 0
let container = try decoder.container(keyedBy: CodingKeys.self)
// how do I generate the key for the correspond "pair" property here?
for key in container.allKeys {
last = try container.decode(Int.self, forKey: CodingKeys(stringValue: "last")!)
pair = try container.decode([[Double]].self, forKey: CodingKeys(stringValue: key.stringValue)!)
}
}
}
init() {
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
let jsonData = Data(jsonString.utf8)
// this gives: "Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "last", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a number instead.", underlyingError: nil))"
let decodedResult = try! JSONDecoder().decode(Pair.self, from: jsonData)
dump(decodedResult)
}
所以我现在明白 CodingKey
一致性正在为序列化数据生成密钥,而不是 Swift 结构(现在我想起来这很有道理)。
那么我现在如何即时生成 pair
的案例,而不是像这样硬编码呢?我知道它与我需要实现的 init(from decoder: Decoder)
有关,但对于我来说,我无法弄清楚它是如何工作的。请帮忙!
编辑 2
好的,我现在很接近了。解码似乎适用于此:
struct Pair: Decodable {
var pair: [[Double]]
var last: Int
private enum CodingKeys : String, CodingKey {
case last
}
private struct DynamicCodingKeys: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
// Use for integer-keyed dictionary
var intValue: Int?
init?(intValue: Int) {
// We are not using this, thus just return nil
return nil
}
}
init(from decoder: Decoder) throws {
// just to stop the compiler moaning
pair = [[]]
last = 0
let container1 = try decoder.container(keyedBy: CodingKeys.self)
last = try container1.decode(Int.self, forKey: .last)
let container2 = try decoder.container(keyedBy: DynamicCodingKeys.self)
for key in container2.allKeys {
pair = try container2.decode([[Double]].self, forKey: DynamicCodingKeys(stringValue: key.stringValue)!)
}
}
}
这段代码似乎完成了它的工作:检查函数本身的 last
和 pair
属性,看起来不错;但我在尝试解码时遇到错误:
init() {
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
let jsonData = Data(jsonString.utf8)
// Fatal error: 'try!' expression unexpectedly raised an error: Swift.DecodingError.typeMismatch(Swift.Array<Any>, Swift.DecodingError.Context(codingPath: [DynamicCodingKeys(stringValue: "last", intValue: nil)], debugDescription: "Expected to decode Array<Any> but found a number instead."
let decodedResult = try! JSONDecoder().decode(Pair.self, from: jsonData)
dump(decodedResult)
}
我离得很近我能尝到它的味道...
你要找的是 JSONSerializer
而不是 JSONDecoder
我猜是 https://developer.apple.com/documentation/foundation/jsonserialization.
因为密钥不可预测,所以最好转换成Dictionary
。或者你可以看看这个https://swiftsenpai.com/swift/decode-dynamic-keys-json/
如果动态密钥在运行时已知,您可以通过解码器的 userInfo
字典传递它。
首先创建两个扩展
extension CodingUserInfoKey {
static let dynamicKey = CodingUserInfoKey(rawValue: "dynamicKey")!
}
extension JSONDecoder {
convenience init(dynamicKey: String) {
self.init()
self.userInfo[.dynamicKey] = dynamicKey
}
}
在结构中实现 CodingKeys
作为能够动态创建键的结构。
struct Pair : Decodable {
let last : Int
let pair : [[Double]]
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
static let last = CodingKeys(stringValue: "last")!
static func makeKey(name: String) -> CodingKeys {
return CodingKeys(stringValue: name)!
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
guard let dynamicKey = decoder.userInfo[.dynamicKey] as? String else {
throw DecodingError.dataCorruptedError(forKey: .makeKey(name: "pair"), in: container, debugDescription: "Dynamic key in userInfo is missing")
}
last = try container.decode(Int.self, forKey: .last)
pair = try container.decode([[Double]].self, forKey: .makeKey(name: dynamicKey))
}
}
现在创建 JSONDecoder
传递已知的动态名称
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
do {
let decoder = JSONDecoder(dynamicKey: "XBTUSD")
let result = try decoder.decode(Pair.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}
编辑:
如果 JSON 始终只包含两个键,这是一种更简单的方法:
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
}
}
struct Pair : Decodable {
let last : Int
let pair : [[Double]]
}
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
let lastPath = codingPath.last!
if lastPath.stringValue == "last" { return lastPath }
return AnyKey(stringValue: "pair")!
})
let result = try decoder.decode(Pair.self, from: Data(jsonString.utf8))
print(result)
} catch {
print(error)
}
我现在有了一些实际有效的代码!
struct Pair: Decodable {
var pair: [[Double]]
var last: Int
private struct CodingKeys: CodingKey {
var intValue: Int?
var stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
static let last = CodingKeys(stringValue: "last")!
static func makeKey(name: String) -> CodingKeys {
return CodingKeys(stringValue: name)!
}
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
last = try container.decode(Int.self, forKey: .last)
let key = container.allKeys.first(where: { [=10=].stringValue != "last" } )?.stringValue
pair = try container.decode([[Double]].self, forKey: .makeKey(name: key!))
}
}
init() {
let jsonString = """
{
"last": 123456,
"XBTUSD": [ [1.0, 2.0, 3.0], [4.0, 5.0, 6.0] ]
}
"""
let jsonData = Data(jsonString.utf8)
// Ask JSONDecoder to decode the JSON data as DecodedArray
let decodedResult = try! JSONDecoder().decode(Pair.self, from: jsonData)
dump(decodedResult)
}