swift-foundation icon indicating copy to clipboard operation
swift-foundation copied to clipboard

[FB13175783] Regression when encoding Float values with JSONEncoder

Open devioustree opened this issue 2 years ago • 1 comments

In iOS 16.* both JSONEncoder and JSONSerialization.data(withJSONObject:) would encode Floats and Double the same. The behaviour of JSONEncoder has changed in iOS 17 and is now not as compatible with JSONSerialization.jsonObject(with:).

I have attached a playground that demonstrates some parsing failures when mixing JSONEncoder encodings with JSONSerialization decodings. Everything works correctly in Xcode 14.3.1 on an iOS 16 simulator. But there will be a fatal error when run on Xcode 15.0 RC on an iOS 17 simulator. The fatal error is due to a failed force casting of NSNumber to Float.

An easier way to see the differences in encoding is to compare the base64 encoded strings of the encoded data:

try print(JSONSerialization.data(withJSONObject: 3.14 as Float, options: [.fragmentsAllowed]).base64EncodedString())
try print(JSONEncoder().encode(3.14 as Float).base64EncodedString())
try print(JSONSerialization.data(withJSONObject: 3.14 as Double, options: [.fragmentsAllowed]).base64EncodedString())
try print(JSONEncoder().encode(3.14 as Double).base64EncodedString())

The results from running this code:

===== iOS 16 =====

My4xNDAwMDAxMDQ5MDQxNzQ4 // Float
My4xNDAwMDAxMDQ5MDQxNzQ4 // Float
My4xNDAwMDAwMDAwMDAwMDAx // Double
My4xNDAwMDAwMDAwMDAwMDAx // Double

===== iOS 17 =====

My4xNDAwMDAxMDQ5MDQxNzQ4    // Float
My4xNA==                    // Float
My4xNDAwMDAwMDAwMDAwMDAx    // Double
My4xNA==                    // Double

JSONEncoder now encodes Float and Double as the same value, which perhaps leads to problems? Alternatively, I noticed that the open source swift-corelibs-foundation project uses NSNumber.doubleValue instead of NSNumber.floatValue in a Float conversion initialiser. I don’t know if the iOS 17 Foundation library is the same, but Float(exactly:) will not be able to handle a lot of (perhaps most?) double values:

Float(exactly: NSNumber(3.14).floatValue) // 3.14
Float(exactly: NSNumber(3.14).doubleValue) // nil

Steps to Reproduce

  • Run playground in Xcode 14.3.1
  • It should run with no errors
  • Run playground in Xcode 15.0 RC
  • A fatal error will occur

Expected Behaviour

No fatal error will occur

JSONEncoderRegression.playground.zip

devioustree avatar Sep 15 '23 23:09 devioustree

For what it's worth, the iOS 17 version is open source in this very library - over here.

parkera avatar Sep 25 '23 23:09 parkera