[swift-users] Parsing Decimal values from JSON
Itai Ferber
iferber at apple.com
Tue Oct 31 12:07:29 CDT 2017
Hi Evtim,
Just want to give some context for this.
This is due to the fact that `JSONEncoder` and `JSONDecoder` are
currently based on `JSONSerialization`: when you go to decode some JSON
data, the data is deserialized using `JSONSerialization`, and then
decoded into your types by `JSONDecoder`. At the `JSONSerialization`
level, however, there is no way to know whether a given numeric value is
meant to be interpreted as a `Double` or as a `Decimal`.
There are subtle differences to decoding as either, so there is no
behavior that could satisfy all use cases. `JSONSerialization` has to
make a decision, so if the number could fit losslessly in a `Double`, it
will prefer that to a `Decimal`. This allows guaranteed precise
round-tripping of all `Double` values at the cost of different behavior
when decoding a `Decimal`.
In practice, this might not really matter in the end based on how you
use the number (e.g. the loss in precision can be so minute as to be
insignificant) — what is your use case here? And can you give some
numeric values for which this is problematic for you?
As others have mentioned, one way to guarantee decoding a numeric string
in a specific way is to actually encode it and decode it as a `String`,
then convert into a `Decimal` where you need it, e.g.
```swift
import Foundation
struct Foo : Codable {
var number: Decimal
public init(number: Decimal) {
self.number = number
}
private enum CodingKeys : String, CodingKey {
case number
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let stringValue = try container.decode(String.self, forKey:
.number)
guard let decimal = Decimal(string: stringValue) else {
throw DecodingError.dataCorruptedError(forKey: .number, in:
container, debugDescription: "Invalid numeric value.")
}
self.number = decimal
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.number.description, forKey: .number)
}
}
let foo = Foo(number: Decimal(string:
"2.71828182845904523536028747135266249775")!)
print(foo) // => Foo(number: 2.71828182845904523536028747135266249775)
let encoder = JSONEncoder()
let data = try encoder.encode(foo)
print(String(data: data, encoding: .utf8)!) // =>
{"number":"2.71828182845904523536028747135266249775"}
let decoder = JSONDecoder()
let decoded = try decoder.decode(Foo.self, from: data)
print(decoded) // => Foo(number:
2.71828182845904523536028747135266249775)
print(decoded.number == foo.number) // => true
```
— Itai
On 28 Oct 2017, at 11:23, Evtim Papushev via swift-users wrote:
> Hello :)
>
> I am trying to find a way to parse a number as Decimal without losing
> the number's precision.
>
> It seems that the JSON decoder parses it as Double then converts it to
> Decimal which introduces errors in the parsing. That behavior is in
> fact incorrect.
>
> Does anyone know if there is a way to obtain the raw data for this
> specific field so I can write the conversion code?
>
> Thanks,
> Evtim
>
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20171031/b07b7273/attachment.html>
More information about the swift-users
mailing list