5

After an Xcode update I encountered a weird problem. In my application, when I get a response from an API, I parse it manually and map it to my model. There are a lot of places in my code where I cast a JSON value to Float with null coalescing like below:

randomVariable = jsonDict["jsonKey"] as? Float ?? 0

It used to work fine but after the update, it would always default to 0. In order to determine the cause, I tried force casting it to Float like below

randomVariable = jsonDict["jsonKey"] as! Float

This resulted in the following error

Unable to bridge NSNumber to Float

Upon further investigation, I found out that Xcode is using Swift 3.3 (previously, it was using Swift 3.2). The solutions I found on StackOverflow revolve around casting it to NSNumber instead of Float. As I mentioned above, I have similar lines of code spread across my app. I was wondering if there is a way to fix this issue. Maybe using an extension of some sort?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Malik
  • 3,763
  • 1
  • 22
  • 35

3 Answers3

15

As you have found, you may need to cast it first to NSNumber.

Something like this:

randomVariable = (jsonDict["jsonKey"] as? NSNumber)?.floatValue ?? 0

Maybe regex replacement would help you update your code.

Pattern: jsonDict\[([^\]]*)\] as\? Float

Replace with: (jsonDict[$1] as? NSNumber)?.floatValue

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • Thanks for the recommendation. I will accept this answer as it does provide a solution. Although in my case, it would result in a massive testing so I just ended up reverting my Xcode back to 9.2 – Malik Apr 04 '18 at 05:24
  • Upvoted, in my case I didn't have lot of float mapping so updated file by file rather than project wise – Vrutin Rathod Apr 10 '18 at 10:07
5

The problem is in 32 bit architecture and updated NSNumber class in swift 4.1.

For example when I write number 1.1 into dictionary it will be stored as 64bit NSNumber with most accurate representation = 1.10000000000000008881784197001...

So, when you read this number as! Float it will fail because 32 bit precision is not good enough to get this most accurate representation.

The most accurate representation in 32 bit = 1.10000002384185791015625

64 bit presentation of float number

So, you need to get truncated result that is implemented inside NSNumber.floatValue() function.

randomVariable = (jsonDict["jsonKey"] as! NSNumber).floatValue
Krešimir Prcela
  • 4,257
  • 33
  • 46
  • This is definitely the issue I was seeing. Tested it against multiple `NSNumber`'s with different significant digits and against different Swift versions. – InkGolem May 09 '18 at 19:30
0

Before I discovered SwiftyJSON, I used to do this, parsing the data manually. You may want to check their source code under the hood to see how it parses your data.

// Sample: rate is a Float

// Number
if let number = dictionary[SerializationKeys.rate] as? NSNumber {
    print("IS A NUMBER")
    rate = number.floatValue
}

// String

if let string = dictionary[SerializationKeys.rate] as? String {
    print("IS A STRING")
    let decimalNumber = NSDecimalNumber(string: string)
    rate = decimalNumber.floatValue
}

And since I've mentioned the SwiftyJSON, I'd like to recommend it to avoid such headache. Leave the parsing of json to that guy. https://github.com/SwiftyJSON/SwiftyJSON

I hope it helps.

Glenn Posadas
  • 12,555
  • 6
  • 54
  • 95
  • I'm aware of `SwiftyJSON` now but I wasn't when I started building this application. A better approach is to update it to Swift 4 since it has its own parser. But in either case, the problem is that the code base is huge and it would take a long time to update all of it. Which is why I was looking for some insight behind this issue in order to come up with a quick fix – Malik Apr 04 '18 at 03:45