tikitu
(Tikitu de Jager)
1
Hi folks,
I'm getting failures decoding json on iOS 17 that was successful on previous versions. The error is complaining that a number in the JSON is not representable in Swift, e.g.
dataCorrupted(
Swift.DecodingError.Context(
codingPath: [],
debugDescription: "The given data was not valid JSON.",
underlyingError: Optional(Error
Domain=NSCocoaErrorDomain
Code=3840
"Number 18.181818181818183 is not representable in Swift."
UserInfo={NSDebugDescription=Number 18.181818181818183 is not representable in Swift.})))
I imagine this is a float/double-representation issue -- possibly previous releases would silently truncate the value, I haven't checked back yet to be sure. It seems the right long-term fix is for me to get the backend to truncate its output to some more sensible precision.
But in the short term, is there anything I can change on the decoding/client side to get this through? I'm perfectly willing to lose precision in decoding, as compared to failing the entire JSON-blob parse.
sveinhal
(Svein Halvor Halvorsen)
2
I guess you can change your models from:
struct Model: Decodable {
//var number: Float
var number: MyFloat
}
And then create a custom decoding for your numbers:
struct MyFloat: Decodable {
var value: Float
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let stringValue = try container.decode(String.self)
// use whatever method to convert the string into a number
self.value = ...
}
}
I guess you could also make a property wrapper, so you don't have to change all call sites to model.number.value.
struct Model: Decodable {
@Truncating var number: Float
}
@propertyWrapper
struct Truncating<F: FloatingPoint>: Decodable {
var wrappedValue: F
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let stringValue = try container.decode(String.self)
// use whatever method to convert the string into a number
self.wrappedValue = ...
}
}
Not tested
1 Like
STREGA
(Dustin Collins)
3
Did you already try changing the property for that decoded value from Float to Double?
That value won't fit into a Float but it should fit into a Double.
tikitu
(Tikitu de Jager)
4
I did try switching Float -> Double, I get the same error. And, extremely weirdly, I also get the same problem with a much lower-precision value: "Number 18.18 is not representable in Swift."
That's bonkers enough that I'm looking elsewhere in my stack to see if something completely off the radar is behaving oddly.
1 Like
STREGA
(Dustin Collins)
5
I can confirm Floats and Double do decode properly in Swift 5.9 (Xcode 15).
The error "The given data was not valid JSON." seems to contradict the number thing. I suspect the number error is just the parser moving on, but the JSON is might be malformed in some way and that's the real issue.
2 Likes
tikitu
(Tikitu de Jager)
6
Thanks for the custom-decoding suggestion. Signs are increasingly pointing to something else being the real problem and the number issue just being a knock-on consequence, but I appreciate the suggestion.
1 Like
tikitu
(Tikitu de Jager)
7
Found the cause. It is a change in behaviour between iOS 16 and 17, but a rather subtle one that shortcuts in my own code happen to turn into the mess you see above.
The change in behaviour is, SingleValueDecodingContainer.decode(Int.self) when decoding a JSON literal such as 18.18 used to throw a DecodingError.dataCorrupted (saying "parsed JSON number <18.18> does not fit in Int"); it now throws a JSONError.numberIsNotRepresentableInSwift.
Mostly that's fine, because mostly when you call decode(Int.self) the kind of failure doesn't much matter to you. But I was using that call as a "probe" to see if I could decode an Int, and catching specific error cases that I knew about. Which didn't include this one, so my retry logic for when the probe fails didn't run. Entirely my bad, as the contract doesn't make any claims whatsoever about what errors may be thrown on failure.
The error in my code is interesting in light of the ongoing discussion of typed throws over on Evolution: I was treating an entirely untyped throw as if I had the safety guarantees of a typed throw.
Why would you want to do that?!
(You may ask why on earth I needed to do that. Story too long for this margin to contain, but I'm using it to capture arbitrary JSON blobs as a sub-part of a generally strongly-typed Codable model. Its a solution, definition not the only solution, and very likely not the best solution either.)
6 Likes
Jon_Shier
(Jon Shier)
8
This logic is in Sources/FoundationEssentials/JSON/JSONDecoder.swift, which appears to only throw numberIsNotRepresentableInSwift, which isn't a good error in cases where the user is simply trying to decode the wrong type. You may want to file an issue on that repo with a link to any feedback you've filed.
4 Likes
tera
9
Nice find, indeed a subtle change.
I guess your implementation would tolerate decoding, say, 18.0 or even 18.0000000000000000123 as Int.
1 Like
How do you catch a let error as JSONError?
I keep getting "Cannot find type 'JSONError' in scope" and I don't know what else to import other than Foundation 
On another note, that typed-throws proposal is 
Nope, it's from Foundation as @tikitu mentioned 
tera
14
I can't find it in public Foundation interfaces so it must be a private type → we are not supposed to use it.
Yeah, hence my comment... I was trying to catch it because it currently messes with some of my decoding implementations... 
tera
16
You want to catch just that error, instead of catching all errors?
I'd do something like this:
try {
...
} catch {
let error = error as NSError
print(error)
// analyse these and act accordingly:
error.code
error.domain
error.userInfo
}