JSON parsing float in swift4.1 has unexpected behaviour - swift

A simple function that parse json variable and returns a float.
func parseMyFloat(jsonString: String) -> Float? {
if let data = jsonString.data(using: String.Encoding.utf8, allowLossyConversion: true),
let parsedJSON = try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String : Any] {
if let parsedJSON = parsedJSON {
return parsedJSON["myFloat"] as? Float
}
}
return nil
}
Now if i try this.
print(parseMyFloat(jsonString: "{\"myFloat\":23.2322998046875}"))
// output: 23.2322998
output is fine but if I change 23.2322998046875 value to 23.2322998046 func returns nil.
print(parseMyFloat(jsonString: "{\"myFloat\":23.2322998}"))
// output: nil
Then I tried casting Any to Float which doesn't work.
let dic:[String : Any] = ["float1" : 23.2322998046875, "float2" : 23.2322998]
print(dic["float1"] as? Float) // prints nil
print(dic["float2"] as? Float) // prints nil
I have lots of float in my code, so after migrating to swift 4.1 I am having this issue.
Should I change all Float's to Double's ?? And 23.2322998046875 why works and why not 23.2322998??

If you want to keep your function as it is and don't want to modify the return value to Double, you can simply parse the JSON value as Double to avoid the issue of casting Any to Float as explained in the comments and then convert the Double to Float before returning it.
func parseMyFloat(jsonString: String) -> Float? {
if let data = jsonString.data(using: String.Encoding.utf8, allowLossyConversion: true), let parsedJSON = (try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)) as? [String : Any], let myDouble = parsedJSON["myFloat"] as? Double {
return Float(myDouble)
}
return nil
}
parseMyFloat(jsonString: "{\"myFloat\":23.2322998046875}") //23.2323998
parseMyFloat(jsonString: "{\"myFloat\":23.2322998}") //23.2323998

Related

Is there a better way to get numbers from dictionary (JSON data)?

Assume I receive data in some JSON which is then parsed to dictionary using a native tool. Some of those values are numbers and naturally I will need to parse them, cast them into what I need in the application.
But the transition from Any to an actual number such as Float, Int, Double seems to be a bit messy. For instance if I expect a double but at some point server returns an integer my code will fail using:
let doubleValue = dictionary["key"] as! Double
So this will work when the item is 1.3 but will fail for 1. To use a bit more concrete example we can use the following:
let myDictionary: [String: Any] = [
"myNumber_int" : 1,
"myNumber_float" : Float(1),
"myNumber_cgFloat" : CGFloat(1),
"myNumber_double" : 1.0
]
let numberInt1 = myDictionary["myNumber_int"] as? Int // 1
let numberInt2 = myDictionary["myNumber_int"] as? Float // nil
let numberInt3 = myDictionary["myNumber_int"] as? CGFloat // nil
let numberInt4 = myDictionary["myNumber_int"] as? Double // nil
let numberFloat1 = myDictionary["myNumber_float"] as? Int // nil
let numberFloat2 = myDictionary["myNumber_float"] as? Float // 1
let numberFloat3 = myDictionary["myNumber_float"] as? CGFloat // nil
let numberFloat4 = myDictionary["myNumber_float"] as? Double // nil
let numberCGFloat1 = myDictionary["myNumber_cgFloat"] as? Int // nil
let numberCGFloat2 = myDictionary["myNumber_cgFloat"] as? Float // nil
let numberCGFloat3 = myDictionary["myNumber_cgFloat"] as? CGFloat // 1
let numberCGFloat4 = myDictionary["myNumber_cgFloat"] as? Double // nil
let numberDouble1 = myDictionary["myNumber_double"] as? Int // nil
let numberDouble2 = myDictionary["myNumber_double"] as? Float // nil
let numberDouble3 = myDictionary["myNumber_double"] as? CGFloat // nil
let numberDouble4 = myDictionary["myNumber_double"] as? Double // 1
So for each type only 1 cast will actually work which is... well I would at least expect that CGFloat will be able to cast directly to Double or Float...
So my solution is using NSNumber:
let myNumbers: [CGFloat] = [
CGFloat((myDictionary["myNumber_int"] as? NSNumber)?.floatValue ?? 0.0),
CGFloat((myDictionary["myNumber_float"] as? NSNumber)?.floatValue ?? 0.0),
CGFloat((myDictionary["myNumber_cgFloat"] as? NSNumber)?.floatValue ?? 0.0),
CGFloat((myDictionary["myNumber_double"] as? NSNumber)?.floatValue ?? 0.0),
] // [1,1,1,1]
This naturally works but the code is pretty ugly for something as simple as this. But putting the code "ugliness" aside; can we not do similar without using Next Step? I mean NSNumber for something as seemingly trivial as this? I am missing something very obvious here, right?
Try like this:-
if let data = myDictionary as? [String: AnyObject] {
let myNumber = data["myNumber_int"] as? Int
}

How to convert a numeric character reference to a decimal in Swift?

How to convert this type of string "8.37" to its decimal value 8.37?
I'm using Swift 4.
I tried:
extension String {
func hexToFloat() -> Float {
var toInt = Int32(truncatingIfNeeded: strtol(self, nil, 16))
var float:Float32!
memcpy(&float, &toInt, MemoryLayout.size(ofValue: float))
return float
}
}
[...]
let myString = "8.37"
let myDecimal = myString.hexToFloat()
print(myDecimal) // prints 0.0
(From here)
There's no straight forward way (at least to my knowledge). But you can do something like below to get the value. Please note that the code below is in Swift 3. You may have to change the syntax.
extension String {
func hexToFloat() -> Float {
let data = myString.data(using: .utf8)
let options: [String: Any] = [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType]
do {
let attributedString = try NSAttributedString(data: data!, options: options, documentAttributes: nil)
return Float(attributedString.string)!
} catch {
return 0
}
}
}
let myString = "8.37"
let myDecimal = myString.hexToFloat()
print(myDecimal) // prints 8.37

cannot subscript a value of type 'inout [AnyHashable: Any

Sorry super noobie here trying to compile this and it's the last error I'm trying to fix.
let optionalString0 = formatter.stringFromNumber(NSNumber(localNotification.userInfo!["pokemonId"]as! Int))
if optionalString0 != nil {
imagen.setImage(UIImage(named: optionalString0!))
I think error is due to this statement:
NSNumber(localNotification.userInfo!["pokemonId"]as! Int)
I tried this in playground to reproduce the error:
var dict = [1: "10", "t": 100] as [AnyHashable : Any]
func foo( paramDict: inout [AnyHashable : Any]) {
print(NSNumber(paramDict["t"] as! Int)) //getting the same error on this line
}
foo(paramDict: &dict)
In above code I get the same error on print statement
I solved this problem by modifying the above code like this:
var dict = [1: "10", "t": 100] as [AnyHashable : Any]
func foo( paramDict: inout [AnyHashable : Any]) {
if let number = paramDict["t"] as? NSNumber {
print(number)
}
}
foo(paramDict: &dict)
so in your case you modify your code like this:
if let number = localNotification.userInfo?["pokemonId"] as? NSNumber {
let optionalString0 = formatter.string(from: number)
if optionalString0 != nil {
imagen.setImage(UIImage(named: optionalString0!))
}
}
Note: You can modify above code like this: (Its not related to the error in question but just a different way to write the above code(may be better))
if let number = localNotification.userInfo?["pokemonId"] as? NSNumber, let optionalString0 = formatter.string(from: number) as? String {
imagen.setImage(UIImage(named: optionalString0))
}
Thanks all for the help and feedback!
this looks like it works for so far
if let number = localNotification.userInfo?["pokemonId"] as? NSNumber, let optionalString0 = formatter.string(from: number) as? String {
imagen.setImage(UIImage(named: optionalString0))
}

Return from initializer without initializing all stored properties error - yet everything is initialized

I'm running into this weird error. it was working fine until I added the image and imageString values and then this error happened:
Return from initializer without initializing all stored properties
I thought I initialized all the properties, not sure why this error is happening. Here is the custom object class
class JSONObject {
private let baseImageURL = "https://website.com"
var airbnbUS: Int
var airbnbLocal: Int
var imageString: String
var image: URL
init(airbnbUS: Int, airbnbLocal: Int, imageString: String, image: URL ){
self.airbnbUS = airbnbUS
self.airbnbLocal = airbnbLocal
self.imageString = imageString
self.image = image
}
init(resultsDictionary:[String: Any]){
guard let cost = resultsDictionary["cost"] as? [String: Any],
let airbnb = cost["airbnb_median"] as? [String : Any],
let usd = airbnb["USD"] as? Int,
let chf = airbnb["CHF"] as? Int
else {
airbnbUS = 0
airbnbLocal = 0
return
}
airbnbUS = usd
airbnbLocal = chf
guard let media = (resultsDictionary["media"] as? [String: Any]),
let imageDictionary = media["image"] as? [String: Any],
let image1000 = imageDictionary["1000"] as? String
else {
imageString = ""
image = URL(string: "\(baseImageURL)")!
return
}
imageString = image1000
image = URL(string: "\(baseImageURL)\(imageString)")!
}
}
The issue is in your resultsDictionary initializer. The return in your first guard statement could return from the initializer early, and the following guard statement (and the code where you assign the image and imageString properties) might not execute.
One solution is to change the first guard statement to an if-let statement.
init(resultsDictionary:[String: Any]){
if let cost = resultsDictionary["cost"] as? [String: Any],
let airbnb = cost["airbnb_median"] as? [String : Any],
let usd = airbnb["USD"] as? Int,
let chf = airbnb["CHF"] as? Int
{
airbnbUS = usd
airbnbLocal = chf
} else {
airbnbUS = 0
airbnbLocal = 0
}
guard let media = (resultsDictionary["media"] as? [String: Any]),
let imageDictionary = media["image"] as? [String: Any],
let image1000 = imageDictionary["1000"] as? String
else {
imageString = ""
image = URL(string: "\(baseImageURL)")!
return
}
imageString = image1000
image = URL(string: "\(baseImageURL)\(imageString)")!
}

possible to cast this Alamofire result to an array of dictionaries

I am not an iOS dev and have to make a few changes to a Swift / AlamoFire project (not mine) and am a bit lost.
I have the following JSON:
{"metro_locations":
[
{
"name":"Ruby Red"
},
{
"name":"Blue Ocean"
}
]
}
class (I know that there are issues here):
class Location{
var name=""
init(obj:tmp){
self.name=tmp["name"]
}
}
and need to make an AlamoFire call
Alamofire.request(.GET, "https://www.domain.com/arc/v1/api/metro_areas/1", parameters: nil)
.responseJSON { response in
if let dataFromNetworking = response.result.value {
let metroLocations = dataFromNetworking["metro_locations"]
var locations: [Location]=[]
for tmp in metroLocations as! [Dictionary] { // <- not working, Generic Paramter 'Key' could not be inferred
let location=Location.init(obj: tmp)
locations.append(location)
}
}
}
I have included the error msg, the "not working" but feel that there are issues in other parts too (like expecting a dictionary in the initialization). What does the 'Key' could not be inferred mean and are there other changes I need to make?
edit #1
I have updated my Location to this to reflect your suggestion:
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] else { return nil }
guard let name = dictionary["name"] else { return nil }
guard let latitude = dictionary["latitude"] else { return nil }
guard let longitude = dictionary["longitude"] else { return nil }
self.name = name as! String
self.id = id as! Int
self.latitude = latitude as! Double
self.longitude = longitude as! Double
}
but I get the error:
Could not cast value of type 'NSNull' (0x10f387600) to 'NSNumber' (0x10f77f2a0).
like this:
I would think that the guard statement would prevent this. What am I missing?
You can cast metroLocations as an array of dictionaries, namely:
Array<Dictionary<String, String>>
Or, more concisely:
[[String: String]]
Thus:
if let dataFromNetworking = response.result.value {
guard let metroLocations = dataFromNetworking["metro_locations"] as? [[String: String]] else {
print("this was not an array of dictionaries where the values were all strings")
return
}
var locations = [Location]()
for dictionary in metroLocations {
if let location = Location(dictionary: dictionary) {
locations.append(location)
}
}
}
Where
class Location {
let name: String
init?(dictionary: [String: String]) {
guard let name = dictionary["name"] else { return nil }
self.name = name
}
}
Clearly, I used [[String: String]] to represent an array of dictionaries where the values were all strings, as in your example. If the values included objects other than strings (numbers, booleans, etc.), then you might use [[String: AnyObject]].
In your revision, you show us a more complete Location implementation. You should avoid as! forced casting, and instead us as? in the guard statements:
class Location {
let id: Int
let name: String
let latitude: Double
let longitude: Double
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let name = dictionary["name"] as? String,
let latitude = dictionary["latitude"] as? Double,
let longitude = dictionary["longitude"] as? Double else {
return nil
}
self.name = name
self.id = id
self.latitude = latitude
self.longitude = longitude
}
}