9

In the former version, to get a float value from a [String: Any] dictionary, I can use let float = dict["somekey"] as? Float, but in swift4.1, it doesn't work. It seems the type of dict["somekey"] has been implicitly inferred as Double before I get it, so casting from Double to Float always fails. I wonder if it is a new characteristic or just a bug.

--Here is the update.

I re-downloadeded an Xcode9.2 and did some experiments, now I think I figure out what's going on. Here is the test code:

let dict: [String : Any] = ["key": 0.1]

if let float: Float = dict["key"] as? Float {
    print(float)
} else {
    print("nil")
}

let dict1: [String : Any] = ["key": NSNumber(value: 0.2)]

if let float: Float = dict1["key"] as? Float {
    print(float)
} else {
    print("nil")
}

let number = NSNumber(value: 0.3)
if let float: Float = number as? Float {
    print(float)
} else {
    print("nil")
}

let number1 = NSNumber(floatLiteral: 0.4)
if let float = number1 as? Float {
    print(float)
} else {
    print("nil")
}

Running this code in Playground of Swift4 and Swift4.1, the results are different. In Swift4, the results are nil 0.2 0.3 0.4, and In Swift4.1 the results are nil nil nil nil. From the result, I can learn two points: 1. When we convert JSON data into a [String : Any] dictionary with the JSONSerialization class, the numeric value is saved as an NSNumber object, but not Int, Double or Float. 2. In Swift4, we can use let float = NSNumberOjbect as? Float to get a Float value, but in Swift4.1 we can't. But still, we can get Int or Double value in this way, either in Swift4 or Swift4.1. Finally again, is this a new feature or a bug? If someone knows, can you guys show up the announcement link?

sepp2k
  • 363,768
  • 54
  • 674
  • 675
LYM
  • 251
  • 3
  • 9
  • Did the compiler say it will always fail? Or did you find out that it always fails by running the code multiple times? – Sweeper Apr 02 '18 at 08:11
  • The behavior is already found in Swift 3, when `dict` is a Swift native `[String: Any]`. When an `NSDictionary` is imported as `[String: Any]`, the behavior is different. – OOPer Apr 02 '18 at 08:16
  • A same line of code, like `guard let float: Float = dict["key"] as? Float else { return }`, can get a float value in swift4.0, but exit in swift4.1. – LYM Apr 02 '18 at 08:59
  • See answers at https://stackoverflow.com/questions/43623530/swift-anyobject-cast-to-float-failed/43623573#43623573 – Ankit Jayaswal Apr 02 '18 at 09:04
  • @Sweeper I tried this "let float: Float? = 0.123 as? Float", and the complier said it will always fail. Now the only way I can get a float value is to use force-casting `Float(someDoubleValue)`. – LYM Apr 02 '18 at 09:09
  • @Ankit Jayaswal It's works. Thank you. I think this coding style is more rigorous. – LYM Apr 02 '18 at 09:16
  • The same code always fails in Swift 3, when `dict` is a Swift native `Dictionary`. Have you really tested the code with Swift native `Dictionary` in Swift 4.0? – OOPer Apr 02 '18 at 09:25
  • For your updated part, check this: [NSNumber bridging and Numeric types](https://github.com/apple/swift-evolution/blob/master/proposals/0170-nsnumber_bridge.md). The article shows the feature is implemented in Swift 4, but the implementation seems to be incomplete in Swift 4 and Swift 4.1 shows the right behavior. Try your sample codes with value `0.5` or `0.25`, which can be represented in `Float` exactly. – OOPer Apr 03 '18 at 16:57

1 Answers1

10

You need to distinguish two cases (in Swift 3, it was three cases):

  • Any containing a Swift native Double
  • Any containing an NSNumber

(In Swift 3, there was type preserving NSNumber case other than the normal NSNumber.)


When you create a native Swift Dictionary such as [String: Any], and set Double value in a normal way like this in your update:

let dict: [String : Any] = ["key": 0.1]

In this case, Any holds the metadata representing Double and the raw value 0.1 as Double.

And casting Any holding Double to Float always fails. As Double to Float cannot be converted with as-castings.

let dblValue: Double = 0.1
if let fltValue = dblValue as? Float { //<-Cast from 'Double' to unrelated type 'Float' always fails
    print(fltValue)
} else {
    print("Cannot convert to Float")
}
//->Cannot convert to Float

But, in case of Any holding NSNumber, as always happens when the Array or Dictionary is bridged from NSArray or NSDictionary, the behaviors are different between former Swifts and Swift 4.1.

The result of JSONSerialization matches this case.

In former Swifts, (normal) NSNumber to Float was an always-success operation.

And in Swift 4.1, the behavior has changed with the new feature which I have shown in my comment:

SE-0170 NSNumber bridging and Numeric types


I omit the third case once found in Swift 3, it's past.

But it is very important how you get your Dictionary or Array, and how you set the value to them, to solve the issue Any to Float.


Finally again, is this a new feature or a bug? If someone knows, can you guys show up the announcement link?

This is a new feature, not a bug. See the link above, and related threads below.

Unable to bridge NSNumber to Float Swift 3.3

Unexpected behavior when casting an NSNumber to Float

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • if ["key": 0.1] then converting to Float failing, but ["key": 1.0], then working successfully, what might be the reason behind this – Mehul Thakkar Jun 25 '18 at 12:49