如何覆盖UnkeyedDecodingContainer协议函数的默认实现?

问题描述 投票:0回答:1
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
解码)。无法覆盖它。
更新2(最小可重现示例):
typealias Money = Dollars<Double>

我在其中使用小型第三方库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"
        ))
    }
}

用途:

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)")
}
swift overriding protocols decodable default-implementation
1个回答
0
投票

让我们首先考虑为什么

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
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.