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)
}
}
This works totally fine ✅
Now I want to do exactly the same, but for UnkeyedDecodingContainer
(in order to decode arrays):
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()
}
}
But these overridden functions are never called 🚨
P.S.
I think the key to the answer lies in the fact that KeyedDecodingContainer
is a struct, while UnkeyedDecodingContainer
is a protocol.
Update:
I know that a canonical way to do this is:
extension Money: Decodable {
init(from decoder: Decoder) throws {
...
}
}
But I can't do that in my situation. Because Money
already conforms to Decodable
, so it already has init(from decoder: Decoder)
defined (it expects to decode from Double
not String
). It's impossible to override it.
Update2 (minimal reproducible example):
typealias Money = Dollars<Double>
where I make use of the tiny third-party library Tagged.
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"
))
}
}
Usage:
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)")
}
Let's first consider why "overriding" methods in KeyedDecodingContainer
works in the first place. The decode
methods you declared are not "overriding" the existing methods as far as the language is concerned - they are just hiding the built-in decode
methods that are in a different module.
When Swift generates Decodable
implementations for PriceData
, it generates calls to KeyedDecodingContainer.decode
. Since you are compiling PriceData
together with the extension
, the built-in decode
methods are hidden. As a result these calls are resolved, they resolve to the decode
methods you defined.
The generated code looks like this:
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)
}
There is no unkeyed decoding container involved in the generated code. It is only after .decode([Money].self)
has been called, and it goes into the Decodable
implementation of Array
, does an UnkeyedDecodingContainer
get involved.
At some point, Array.init(from:)
would call UnkeyedDecodingContainer.decode
, but this call has already been resolved! The Swift standard library has already been compiled, and it wouldn't know about your extension
.
To work around this, you can add a decode(_ type: [Money].Type)
overload in your 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
}
}
An alternative would be to just create a Decodable
type that wraps a [Money]
, instead of "overriding" KeyedDecodingContainer.decode
.