extension KeyedDecodingContainer {
func decode(_: Money.Type, forKey key: Key) throws -> Money {
let str = try decode(String.self, forKey: key)
return try str.toMoney(on: key)
}
func decodeIfPresent(_: Money.Type, forKey key: Key) throws -> Money? {
let str = try decodeIfPresent(String.self, forKey: key)
return try str?.toMoney(on: key)
}
}
这完全没问题 ✅
现在我想做完全相同的事情,但是对于
UnkeyedDecodingContainer
(为了解码数组):
extension UnkeyedDecodingContainer {
mutating func decode(_: Money.Type) throws -> Money {
let str = try decode(String.self)
return try str.toMoney()
}
mutating func decodeIfPresent(_: Money.Type) throws -> Money? {
let str = try decodeIfPresent(String.self)
return try str?.toMoney()
}
}
但是这些被重写的函数永远不会被调用🚨
PS
我认为答案的关键在于
KeyedDecodingContainer
是一个结构体,而UnkeyedDecodingContainer
是一个协议。extension Money: Decodable {
init(from decoder: Decoder) throws {
...
}
}
但以我的情况我不能这样做。因为
Money
已经符合 Decodable
,所以它已经定义了 init(from decoder: Decoder)
(它期望从 Double
而不是 String
解码)。无法覆盖它。typealias Money = Dollars<Double>
extension String {
func toMoney(on key: CodingKey? = nil) throws -> Money {
if let money = Money(self) { return money }
throw DecodingError.dataCorrupted(.init(
codingPath: key.map { [$0] } ?? [],
debugDescription: "Can't convert JSON String to Money"
))
}
}
用途:
struct PriceData: Decodable {
let price: Money?
let prices: [Money]
}
// NOTE: String data
let jsonData = """
{
"price": "10.5",
"prices": ["5.3", "7.1", "9.6"]
}
""".data(using: .utf8)!
do {
let coin = try JSONDecoder().decode(PriceData.self, from: jsonData)
print("Price: ", coin.price)
print("Prices: ", coin.prices)
} catch {
print("Error decoding JSON: \(error)")
}
让我们首先考虑为什么
KeyedDecodingContainer
中的“重写”方法首先起作用。就语言而言,您声明的 decode
方法不会“覆盖”现有方法 - 它们只是隐藏不同模块中的内置 decode
方法。
当 Swift 为
Decodable
生成 PriceData
实现时,它会生成对 KeyedDecodingContainer.decode
的调用。当这些调用得到解析时,它们会解析为您定义的 decode
方法,因为这些调用与 extension
位于同一模块中。内置的 decode
方法是隐藏的。生成的代码如下所示:
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
// this call will resolve to the deocdeIfPresent you defined
self.price = try container.decodeIfPresent(Money.self, forKey: .price)
// this call will resolve to the built-in decode method
self.prices = try container.decode([Money].self, forKey: .prices)
}
生成的代码中不涉及未加密的解码容器。只有在调用了
.decode([Money].self)
并进入 Decodable
的 Array
实现之后,UnkeyedDecodingContainer
才会介入。在某些时候,Array.init(from:)
会调用UnkeyedDecodingContainer.decode
,但是这个调用已经被解析了!Swift标准库已经编译好了,它不会知道你的extension
。
要解决此问题,您可以在
decode(_ type: [Money].Type)
中添加 extension
重载。
extension KeyedDecodingContainer {
func decode(
_ type: [Money].Type,
forKey key: Key
) throws -> [Money] {
var unkeyedContainer = try nestedUnkeyedContainer(forKey: key)
var result = [Money]()
while !unkeyedContainer.isAtEnd {
let str = try unkeyedContainer.decode(String.self)
result.append(try str.toMoney())
}
return result
}
}