How to Bridge Nsnumber to Float in JSON Parsing

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? for NSNumber 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
}
  1. This calls Double._forceBridgeFromObjectiveC which calls Double(exactly:) with Double, Int64, or UInt64 based on the stored type in rating. It will fail and crash the app if the number isn't exactly representable as a Double. E.g. UInt64.max has more digits than Double can store, so it'll crash.

  2. This is exactly the same as 1 except that it may also crash on NaN since that check isn't included.

  3. This function always returns a Double but will lose precision in cases where 1 and 2 would crash. This literally just calls doubleValue when passing in an NSNumber.

  4. Same as 3.

  5. 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.

  6. 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
  1. The first item takes advantage of Objective-Cbridging which allows AnyObject and NSNumber to be cast as Double|Float|Int|UInt|Bool.
  2. 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 passing AnyObject in generated an error that it cannot be implicitly downcast to NSNumber so it must be there and not just bridging.
  3. The third item doesn't employ language features in the same way. It just takes advantage of the fact that doubleValue returns a Double.

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



Leave a reply



Submit