使 NSDecimalNumber 可编码

Making NSDecimalNumber Codable

是否可以扩展 NSDecimalNumber 以符合 Encodable & Decodable 协议?

在 swift 中你应该使用 Decimal 类型。此类型确认协议 Encodable & Decodable 开箱即用。

如果您有 NSDecimalNumber 类型的代码,很容易将其转换为 Decimal

let objcDecimal = NSDecimalNumber(decimal: 10)
let swiftDecimal = (objcDecimal as Decimal)

无法扩展 NSDecimalNumber 以符合 EncodableDecodable 协议。 Jordan Rose 解释如下 swift evolution email thread.

如果你需要 NSDecimalValue 输入你的 API 你可以在 Decimal.

周围构建计算 属性
struct YourType: Codable {
    var decimalNumber: NSDecimalNumber {
        get { return NSDecimalNumber(decimal: decimalValue) }
        set { decimalValue = newValue.decimalValue }
    }
    private var decimalValue: Decimal
}

顺便说一句。如果您使用 NSNumberFormatter 进行解析,请注意 known bug 在某些情况下会导致精度损失。

let f = NumberFormatter()
f.generatesDecimalNumbers = true
f.locale = Locale(identifier: "en_US_POSIX")
let z = f.number(from: "8.3")!
// z.decimalValue._exponent is not -1
// z.decimalValue._mantissa is not (83, 0, 0, 0, 0, 0, 0, 0)

以这种方式解析字符串:

NSDecimalNumber(string: "8.3", locale: Locale(identifier: "en_US_POSIX"))

使用 Swift 5.1 您可以使用 属性 包装器来避免编写自定义 init(from decoder: Decoder) / encode(to encoder: Encoder) 的样板.

@propertyWrapper
struct NumberString {
    private let value: String
    var wrappedValue: NSDecimalNumber

    init(wrappedValue: NSDecimalNumber) {
        self.wrappedValue = wrappedValue
        value = wrappedValue.stringValue
    }
}

extension NumberString: Decodable {
    init(from decoder: Decoder) throws {
        value = try String(from: decoder)
        wrappedValue = NSDecimalNumber(string: value)
    }
}

extension NumberString: Encodable {
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        try container.encode(wrappedValue.stringValue)
    }
}

extension NumberString: Equatable {}

用法:

struct Foo: Codable {
    @NumberString var value: NSDecimalNumber
}