Wrap your precise values into a custom struct
with custom decoding:
public struct PreciseNumber: Codable {
public let value: Decimal
private static let posixLocale = Locale(identifier: "en_US_POSIX")
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let rawValue = try container.decode(String.self)
value = NSDecimalNumber(string: rawValue, locale: Self.posixLocale) as Decimal
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode((value as NSDecimalNumber).description(withLocale: Self.posixLocale))
}
}
Then you directly decode PreciseNumber
:
public var amount: PreciseNumber?
I have intentionally used Decimal
instead of Double
because a Double
would lose precision.
A slightly more complex solution is to use a property wrapper:
@propertyWrapper
public struct NumberAsString: Codable {
public var wrappedValue: Decimal?
private static let posixLocale = Locale(identifier: "en_US_POSIX")
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let rawValue = try container.decode(String.self)
guard !container.decodeNil() else {
wrappedValue = nil
return
}
wrappedValue = NSDecimalNumber(string: rawValue, locale: Self.posixLocale) as Decimal
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
guard let wrappedValue = wrappedValue else {
try container.encodeNil()
return
}
try container.encode((wrappedValue as NSDecimalNumber).description(withLocale: Self.posixLocale))
}
public init(wrappedValue: Decimal?) {
self.wrappedValue = wrappedValue
}
}
used as:
public struct MyStruct: Codable {
@NumberAsString public var amount: Decimal?
public init(amount: Decimal? = nil) {
self.amount = amount
}
}