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

- 33,216
- 24
- 116
- 190

- 772
- 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
4 Answers
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"))

- 2,788
- 15
- 13
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)

- 202
- 2
- 5
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
}

- 587
- 7
- 16
In my case, We are maintaining legacy code which is Objective-C and Swift One of the modules we needed to have a property of type NSNumber (internal API reason) which is not supported by Codable So We use Codable for almost all supported data types and NSCoding with a help of NSKeyedUnarchiver for unsupported types
I am sharing here a sample of the code, as a reference that might help someone who has a such scenario.
class Branch: NSObject, Codable {
@objc var discountMaxLimit: NSNumber?
private enum CodingKeys: String, CodingKey {
case discountInfoKeys
}
private enum CorporateDiscountInfoKeys: String, CodingKey {
case discountMaxLimit = "discount_max_limit"
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var discountInfoK = container.nestedContainer(keyedBy: discountInfoKeys.self, forKey: .discountInfoKeys)
if let value = discountMaxLimit {
let data = try NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: false)
try discountInfoK.encode(data, forKey: .discountMaxLimit)
}
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let discountInfo = try container.nestedContainer(keyedBy: discountInfoKeys.self, forKey: .discountInfoKeys)
let discountMaxLimitData = try discountInfo.decode(Data.self, forKey: .discountMaxLimit)
discountMaxLimit = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(discountMaxLimitData) as? NSNumber
}
}

- 3,711
- 1
- 45
- 37