12

Is it possible to extend NSDecimalNumber to conform Encodable & Decodable protocols?

Lorenzo B
  • 33,456
  • 23
  • 115
  • 188
Arda Keskiner
  • 742
  • 7
  • 23
  • What have you tried so far? What are your requirements? What are you trying to encode / decode? – Lorenzo B Sep 19 '17 at 07:45
  • @LorenzoB I checked Apple's documentation https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types for encoding and decoding custom types. I'm trying to parse responses from server which I'd loose precision if I used Double. – Arda Keskiner Sep 19 '17 at 07:54

3 Answers3

13

It is not possible to extend NSDecimalNumber to conform to Encodable & Decodable protocols. Jordan Rose explains it in the following swift evolution email thread.

If you need NSDecimalValue type in your API you can build computed property around Decimal.

struct YourType: Codable {
    var decimalNumber: NSDecimalNumber {
        get { return NSDecimalNumber(decimal: decimalValue) }
        set { decimalValue = newValue.decimalValue }
    }
    private var decimalValue: Decimal
}

Btw. If you are using NSNumberFormatter for parsing, beware of a known bug that causes precision loss in some cases.

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)

Parse strings this way instead:

NSDecimalNumber(string: "8.3", locale: Locale(identifier: "en_US_POSIX"))
Wojciech Nagrodzki
  • 2,708
  • 14
  • 12
3

In swift you should use Decimal type. This type confirms to protocols Encodable & Decodable from the box.

If you have NSDecimalNumber type in your code it's easy to cast it to Decimal

let objcDecimal = NSDecimalNumber(decimal: 10)
let swiftDecimal = (objcDecimal as Decimal)
Nick Rybalko
  • 182
  • 1
  • 5
0

With Swift 5.1 you can use property wrappers to avoid the boilerplate of writing a custom 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 {}

Usage:

struct Foo: Codable {
    @NumberString var value: NSDecimalNumber
}
boa_in_samoa
  • 487
  • 7
  • 15