Unable to bridge NSNumber to Float in JSON parsing - ios

Get this error:
Fatal error: Unable to bridge NSNumber to Float. What is the problem?
This is the original message, it is float and not string.
{\"name\":\"Tomas\",\"gender\":\"male\",\"probability\":0.99,\"count\":594}

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.

You are using the wrong API.
Don’t use KVC (valueForKey) unless you really need KVC.
For getting a dictionary value use always key subscription (or objectForKey)
if let probability = d["probability"] as? Float {
print(probability)
}

I would recommend ditching the dictionary representation entirely and moving to a type-safe parser using Decodable:
struct User: Decodable {
let name: String
let gender: String
let probability: Float
let count: Int
}
let str = "{ \"name\": \"Tomas\", \"gender\": \"male\", \"probability\":0.99, \"count\": 594 }"
let data = str.data(using: .utf8)!
do {
let user = try JSONDecoder().decode(User.self, from: data)
} catch {
// handle errors
}

Some inside baseball here:
When swift is casting a floating point number from json to float, it has some kind of validation that makes sure the number lands on a terminating fractional value that the float data type can support.
E.g. if your value was 0.25 (1/4th), 0.875 (7/8ths), etc., it would not give you an error.
If your value does not fall on one of these terminating fractions (e.g. 0.33), it assumes some data could be lost to the lack of precision, and it throws an error.
If you don't care about the potential data loss, here is one way to convert the value to a float without much worry:
Float(jsonDictionary["jsonDictionaryKey"] as! Double)

Related

how to convert __SwiftValue to Int or String from web3swift?

I have data fetched from a network request, which is ["0": 1]. I want to parse the value 1 into either Int or String.
If I try to cast the value to either String, Int, NSNumber, or NSString, I invariably get the following error:
let value = data["0"] as! Int
Could not cast value of type '__SwiftValue' (0x7fff873c5bd8) to 'NSNumber' (0x7fff86d8c858).
or NSString.
When I checked:
let isValidJSON = JSONSerialization.isValidJSONObject(data)
this returns false, which means the following fails as well:
let json = try JSONSerialization.data(withJSONObject: data, options: .prettyPrinted)
How do I parse this?
I'm using a library named web3swift and the data is returned from this function:
public func call(transactionOptions: TransactionOptions? = nil) throws -> [String: Any] {
return try self.callPromise(transactionOptions: transactionOptions).wait()
}
And callPromise in turn returns the following:
public func callPromise(transactionOptions: TransactionOptions? = nil) -> Promise<[String: Any]>
I digged into the library's code, and found that the library decodes the integers in the response as either BigInt or BigUInt. You should know which kind of data you are expecting.
You should try casting to one of those types instead, and since BigInt or BigUInt can store numbers that are arbitrarily big, you probably shouldn't convert them to an Int. You can convert them to Strings (not sure why you want to) using String.init(_:).
This is not one of the types of object that JSONSerialization recognises, so JSONSerialization.isValidJSONObject returns false.

Parse doubles such as 1.0 from JSON in Swift 4 without loosing the decimal?

or can I check if a number was decoded as a decimal number and not and integer later?
if let int = any as? Int {
print("Object in an integer")
} else if let num = any as? Double {
print("Object in a double")
}
, where "any" is an Any value and = 1.0 (not a string) in the JSON file. "any" can be cast to both integer and double (so the order of which I check determines the outcome), but I would like to keep the original format from the JSON file.
Decoding is done using the following line:
let json = try JSONSerialization.jsonObject(with: data, options: [])
Edit: I've tried checking CFType, but get the same for both 1 and 1.0 (inspired by http://stackoverflow.com/a/30223989/1694526)
Any ideas?
As already mentioned by #Sulthan this is not possible on the level you are working as JSONSerialization will and should use a single class to represent a value and may not determine its type.
You could try finding some other tool to check for values but does it really make sense?
You are trying to look for differences between Int and Double but what about 64 or 32 bit? Or signed and unsigned? We usually don't write those into strings so there really is no way to distinguish between them. So there is really no general logic in doing so.
Are you sure the returned JSON will always have ".0" appended for these values? This really depends on the system and a smallest optimization would trim that because JSON standard does not include precisions on numbers. For instance if I use JSONSerialization and print out String(data: (try! JSONSerialization.data(withJSONObject: [ "value": 1.0 ], options: .prettyPrinted)), encoding: .utf8) I receive: {\n \"value\" : 1\n} which means it trimmed ".0" anyway.
I find it hard to understand how this would be good structurally. If you need to save these data for instance into your database you will need to define the size and type of the primitive to hold your data. If you need to use some arithmetics you again need to specify the type...
The only way would be to use it as a display string. But in that case your value should be returned as a string and not as a number.
The solution is to parse to an NSNumber and then to a Decimal (or NSDecimalNumber). DO NOT parse via a Double.
let jsonString = "[ 4.01 ]"
let jsonData = jsonString.data(using: .utf8)!
let jsonArray = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [Any]
// This is the WRONG way to parse decimals (via a Double)
// parseAttemptA = 4.009999999999998976
let parseAttemptA: Decimal = Decimal(jsonArray[0] as! Double)
// This is the CORRECT way to parse decimals (via an NSNumber)
// parseAttemptB = 4.01
let parseAttemptB: Decimal = (jsonArray[0] as! NSNumber).decimalValue
Here's a screenshot of a playground...

swift 3, JSON, invalid top-level type when writing

I have two exactly json objects, one is created from a function, the other is hard-coded, the hardcoded one works, the other doesn't (it always complains about the error invalid top-level type , which is weird. Any tip? Tks
let myData = self.dailyMileage?.toDictionary()
let directData = ["orgId" : self.orgId, "driverId" : self.driverId, "date" : Utils.getTodaysDate() ] as [String : Any]
//this won't work unless I substitute myData with directData
let jsonData = try JSONSerialization.data(withJSONObject: myData, options: .prettyPrinted)
//this is the function that produces myData, and nothing is nil
public func toDictionary() -> [String : Any] {
let dict = [ "orgId" : orgId , "driverId": driverId, "date" : date] as [String : Any]
return dict
}
JSONSerialization as given in the documentation:
An 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.
Other rules may apply. Calling isValidJSONObject(_:) or attempting a conversion are the definitive ways to tell if a given object can be converted to JSON data.
I think the one that's coming from the function might have an NSDate object instead of an NSString object.
The other reason is because your myData object is optional. JSONSerialization may give an error like that is the object is optional.
Please check if it is due to one of those two reasons. Feel free to suggest edits to make it better.

How to cast Dictionary in Swift to related type?

This is what I am trying to do with the dictionary:
if let deliveries = dictionary["deliveries"] as? NSDictionary {
var castedDeliveries = [Double: Double]()
for delivery in deliveries {
if let value = delivery.value as? Double {
castedDeliveries[Double(delivery.key as! NSNumber)] = value //Could not cast value of type 'NSTaggedPointerString' (0x1a1e3af20) to 'NSNumber' (0x1a1e458b0).
}
}
settings!.deliveries = castedDeliveries
}
And this is what I try to cast, as a part of JSON response from server:
deliveries = {
2 = 0;
5 = "2.59";
7 = "3.59";
};
It doesnt work, because there is an error at commented line:
Could not cast value of type 'NSTaggedPointerString' (0x1a1e3af20) to 'NSNumber' (0x1a1e458b0).
You are trying to cast dictionary directly but instead you need to cast each key - value pair. If you want generic solution to this problem take a look at SwiftyJSON library which address JSON parsing problem for you.
Casting doens't mean data transformation from a type to another.
Your dictionary seems to be composed by Integer keys and String values.
If you want to transform in something else you ca use the map function.
let converted = deliveries.map{[Double($0) : Double($1)]}
But pay attention.
Here we are saying, iterate over the dictionary (in the $0 there is the dictionary key in the $1 there is the value) and create a new dictionary that has as a key a Double initialized at the key value and as a new value a Double initialized as the old dictionary value. The last conversion can fail, so the returned data is an optional.
As I noted in the comments, this isn't casting. You want a data conversion. You need to do that explicitly, especially in this case since it might fail.
Looking at the error, I think you really have a dictionary of [String:String] here (in NSDictionary form). That suggests the JSON is badly encoded, but such is life. Assuming that dictionary looks something like this:
let dictionary: NSDictionary = ["deliveries": ["2":"0", "5": "2.59", "7": "3.59"]]
You would convert it to [Double:Double] like this:
if let jsonDeliveries = dictionary["deliveries"] as? [String:String] {
var deliveries: [Double: Double] = [:]
for (key, value) in jsonDeliveries {
if let keyDouble = Double(key),
valueDouble = Double(value) {
deliveries[keyDouble] = valueDouble
}
}
// Use deliveries
}
This silently ignores any values that can't be converted to Double. If you would rather generate errors, use a guard let rather than an if let.

Getting NSNumber from nsdictionary in swift

I have an NSDictionary which I created after parsing an XML file. It is storing some numerical value (e.g, 6757677) as a string. Now I want to read it into an NSNumber. I have tried following:
alert.versionNumber = dictRef["VersionNumber"] as! NSNumber
But I am getting following error:
Could not cast value of type '__NSCFString' (0x1081c9c50) to 'NSNumber' (0x1076abb88).
I have also tried following but getting same error:
let refID:NSNumber = dictRef["referralID"] as! NSNumber
alert.refID = refID.integerValue
These conversions were simple in Objective C but are giving me a hard time in Swift.
You need to convert the NSString into a NSNumber by taking a detour via a long long (the largest signed integer type) or double (the largest floating point type). For example:
let string : NSString = "42"
// Integer solution:
let intermediate = string.longLongValue
let number = NSNumber(longLong: intermediate)
// Floating point solution:
let intermediate2 = string.doubleValue
let number2 = NSNumber(double: intermediate2)
Note that for converting a string to its double value, you might need to use a NSNumberFormatter to correctly parse the string and handle locale-dependent differences (for example, the decimal separator is a . in the US but a , in Germany).
Try this
var num:NSNumber = NSNumber(integer:(dictRef["referralID"] as! NSString).integerValue)
let dictRef = NSDictionary(objects: ["6757677","1234567"], forKeys: ["VersionNumber", "referralID"])
if let versionNumber = dictRef.valueForKey("VersionNumber")?.integerValue {
let myNSNumber = NSNumber(integer: versionNumber)
println(myNSNumber) // 6757677
}

Resources