Unable to bridge NSNumber to Float in JSON parsing
You have many different types of numbers in Swift/Foundation. Your NSKeyValueCoding
has been set as instance of NSNumber
(see excerpt of JSON serialization documentation below) so you need to read as is, and then ask to convert this NSNumber
as Float
if needed:
if let n = d.value(forKey: "probability") as? NSNumber {
let f = n.floatValue
}
JSONSerialization
documentation says:
A Foundation object that may be converted to JSON must have the
following properties:
- The top level object is an NSArray or
NSDictionary.- All objects are instances of NSString, NSNumber,
NSArray, NSDictionary, or NSNull.- All dictionary keys are instances of NSString.
- Numbers are not NaN or infinity.
Unable to bridge NSNumber to Float Swift 3.3
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
Swift Unable to bridge NSNumber to Float
I am able to reproduce this problem with this simple example:
var arr = [NSNumber(value: 1.3 as Float), NSNumber(value: 2.7)]
// This works
let x = arr.map { $0.floatValue }
print(x)
[1.3, 2.7]
// This fails with error 'Fatal error: Unable to bridge NSNumber to Float'
let y = arr as! [Float]
print(y)
As explained in the duplicate's answer, you can't cast an NSNumber
to Float
if the cast doesn't preserve the exact value. Double(2.7)
loses precision when converted to Float
, so the cast fails. Had I chosen 2.5
instead of 2.7
for the Double
, the cast would have worked.
Accessing the value with .floatValue
works because it uses the property of NSNumber
to produce the Float
value.
Unable to bridge NSNumber to Float Swift 3.3
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
Unexpected behavior when casting an NSNumber to Float
This is a consequence of SE-0170 NSNumber bridging and Numeric types, implemented in Swift 4:
as?
forNSNumber
should mean "Can I safely express the value stored in this opaque box called a NSNumber as the value I want?".
1.12
is a floating point literal, and inferred as a Double
, so NSNumber(value: 1.12)
is “boxing” the 64-bit floating point value
closest to 1.12
. Converting that to a 32-bit Float
does not
preserve this value:
let n = NSNumber(value: 1.12)
let x = Float(truncating: n) // Or: let x = n.floatValue
let nn = NSNumber(value: x)
print(n == nn) // false
On the other hand, 1.0
can be represented exactly as a Float
:
let m = NSNumber(value: 1.0)
let y = m.floatValue
let mm = NSNumber(value: y)
print(m == mm) // true
and that is why casting m as? Float
succeeds. Both
n.floatValue
Float(truncating: n)
can be used to ”truncate” the number to the closest representable
32-bit floating point value.
Swift convert object that is NSNumber to Double
Update
Swift's behavior here has changed quite a bit since 1.0. Not that it was that easy before, but Swift has made it harder to convert between number types because it wants you to be explicit about what to do with precision loss. Your new choices now look like this:
var rating: NSNumber
var ratingDouble: Double
ratingDouble = rating as! Double // 1
ratingDouble = Double(exactly: rating)! // 2
ratingDouble = Double(truncating: rating) // 3
ratingDouble = rating.doubleValue // 4
if let x = rating as? Double { // 5
ratingDouble = x
}
if let x = Double(exactly: rating) { // 6
ratingDouble = x
}
This calls
Double._forceBridgeFromObjectiveC
which callsDouble(exactly:)
withDouble
,Int64
, orUInt64
based on the stored type inrating
. It will fail and crash the app if the number isn't exactly representable as aDouble
. E.g.UInt64.max
has more digits thanDouble
can store, so it'll crash.This is exactly the same as 1 except that it may also crash on
NaN
since that check isn't included.This function always returns a
Double
but will lose precision in cases where 1 and 2 would crash. This literally just callsdoubleValue
when passing in anNSNumber
.Same as 3.
This is like 1 except that instead of crashing the app, it'll return nil and the inside of the statement won't be evaluated.
Same as 5, but like 2 will return nil if the value is
NaN
.
If you know your data source is dealing in doubles, 1-4 will probably all serve you about the same. 3 and 4 would be my first choices though.
Old Answer for Swift 1 and 2
There are several things you can do:
var rating: NSNumber
var ratingDouble: Double
ratingDouble = rating as Double // 1
ratingDouble = Double(rating) // 2
ratingDouble = rating.doubleValue // 3
- The first item takes advantage of
Objective-C
bridging which allowsAnyObject
andNSNumber
to be cast asDouble|Float|Int|UInt|Bool
. - The second item presumably goes through a constructor with the signature
init(_ number: NSNumber)
. I couldn't find it in the module or docs but passingAnyObject
in generated an error that it cannot be implicitly downcast toNSNumber
so it must be there and not just bridging. - The third item doesn't employ language features in the same way. It just takes advantage of the fact that
doubleValue
returns aDouble
.
One benefit of 1 is that it also works for AnyObject
so your code could be:
let ratingDouble = self.prodResult!.prodsInfo.prodList[indexPath.row].avgRating! as Double
Note that I removed the ?
from your function and moved the !
in. Whenever you use ! you are eschewing the safety of ?
so there's no reason to do both together.
Swift Unable to bridge NSNumber to Int
You didn't include the value of the number in your question so I can't confirm this 100%. However, since you said it works fine on newer devices but fails on the iPhone 5, I think you're dealing with a 32-bit vs 64-bit issue.
If you google this, you'll find the following:
beginning with the iPhone 5S and the iPad Air in 2013, all of Apple’s
iPhones, iPads, and iPods since have included 64-bit chips.
According to the Swift Documentation: link
Int In most cases, you don’t need to pick a specific size of integer
to use in your code. Swift provides an additional integer type, Int,
which has the same size as the current platform’s native word size:On a 32-bit platform, Int is the same size as Int32.
On a 64-bit platform, Int is the same size as Int64.
Unless you need to work with a specific size of integer, always use
Int for integer values in your code. This aids code consistency and
interoperability. Even on 32-bit platforms, Int can store any value
between -2,147,483,648 and 2,147,483,647, and is large enough for many
integer ranges.
Is your value outside the 32-bit Int range?
Related Topics
How to Create a Pfquerytableviewcontroller with Parse in Swift
My Uiviews Muck-Up When I Combine Uipangesturerecognizer and Autolayout
How to Add Images as Text Attachment in Swift Using Nsattributedstring
Swift - Uipopovercontroller in iOS 8
Determine If Mkmapview Was Dragged/Moved in Swift 2.0
How to Simultaneously Satisfy Constraints with Keyboard and Uitoolbar
String to Double in Xcode 6's Swift
How to Use Type Erasure with a Protocol Using Associated Type
Didbegincontact Passed Pkphyicsobject
iOS Swift3 Check Nil Value for Viewcontroller Object
How to Programmatically Check and Open an Existing App in iOS 8
iOS - Send Image to Instagram - Documentinteraction
Transition Delegate for Uitabbarcontroller Animation
How to Convert Date (Without Knowing the Kind) to String
Uilocalnotification: Playing a Custom Audio File Saved in Documents Directory