After an Xcode update I encountered a weird problem. In my application, when I get a response from an API, I parse it manually and map it to my model. There are a lot of places in my code where I cast a JSON value to Float with null coalescing like below:
randomVariable = jsonDict["jsonKey"] as? Float ?? 0
It used to work fine but after the update, it would always default to 0. In order to determine the cause, I tried force casting it to Float like below
randomVariable = jsonDict["jsonKey"] as! Float
This resulted in the following error
Unable to bridge NSNumber to Float
Upon further investigation, I found out that Xcode is using Swift 3.3 (previously, it was using Swift 3.2). The solutions I found on StackOverflow revolve around casting it to NSNumber instead of Float. As I mentioned above, I have similar lines of code spread across my app. I was wondering if there is a way to fix this issue. Maybe using an extension of some sort?
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
The problem is in 32 bit architecture and updated NSNumber class in swift 4.1.
For example when I write number 1.1 into dictionary it will be stored as 64bit NSNumber with most accurate representation = 1.10000000000000008881784197001...
So, when you read this number as! Float it will fail because 32 bit precision is not good enough to get this most accurate representation.
The most accurate representation in 32 bit = 1.10000002384185791015625
64 bit presentation of float number
So, you need to get truncated result that is implemented inside NSNumber.floatValue() function.
randomVariable = (jsonDict["jsonKey"] as! NSNumber).floatValue
Before I discovered SwiftyJSON, I used to do this, parsing the data manually. You may want to check their source code under the hood to see how it parses your data.
// Sample: rate is a Float
// Number
if let number = dictionary[SerializationKeys.rate] as? NSNumber {
print("IS A NUMBER")
rate = number.floatValue
}
// String
if let string = dictionary[SerializationKeys.rate] as? String {
print("IS A STRING")
let decimalNumber = NSDecimalNumber(string: string)
rate = decimalNumber.floatValue
}
And since I've mentioned the SwiftyJSON, I'd like to recommend it to avoid such headache. Leave the parsing of json to that guy. https://github.com/SwiftyJSON/SwiftyJSON
I hope it helps.
Related
I'm a very new developer(this is my first dev job) building a Swift/iOS application for creating/processing orders, and the server is sending me a ObjectID() object in JSON with every product I look up. After some research, it seems like this is a MongoDB object.
The gentleman coding my API routes wants me to grab that object for every product the server sends me, so I can send it back to the server with any orders that include that product. He says that will make it much easier for him to access the product when processing new orders.
So far, I've had no trouble decoding the JSON the server is sending me, because it's been in formats like String, Int, Float, etc., or just another JSON object that needs a new Codable struct full of more of the same.
When it comes to creating a Codable struct for an ObjectID, I don't know what keys/value types (properties/value types, whatever terminology you want to use) to tell it to expect. Or if this is even the correct way to go about it.
This is what I have right now:
import Foundation
struct ProductData: Codable {
let _id : ObjectId
let productId : String
let description : String
let ...
}
The ObjectId type appearing above is a custom Codable struct that I haven't built yet, because I'm not sure how. I imagine it should look something like this:
import Foundation
struct ObjectId : Codable {
let someVariableName : SomeCodableType
let ...
}
I don't know what the variable name or the type would be. I understand that it has a timestamp and some other information inside of it, and I've read about it being represented as a string, but I get the feeling if I try something like let _id:String in my product Codable struct, it won't decode/encode the way I'm imagining.
I'm wondering how to build a "type" that will properly catch/decode the _id object that is being thrown at me. If there's a way to simply hold that data without decoding it, and just send it back when I need to later, that would also suit my purposes.
EDIT:
After some experimentation, I found this raw JSON for the _id object:
"_id":{"$id":"58071f9d3f791f4f4f8b45ff"}
Dollar signs are not allowed in Swift variable/property names, so I'm unsure how to proceed in a way that satisfies both the incoming AND outgoing - I could make a custom key by manually initializing the JSON so it will properly work with Swift, but I'm unsure if there's a way to reverse that when encoding the same object back into JSON to send back to the server.
Catching the JSON as a Dictionary of [String:String] seemed to do the trick.
import Foundation
struct ProductData: Codable {
let _id : [String:String]
let productId : String
let description : String
let ...
}
However, the server is struggling to convert this back into an OrderId() object - I'm guessing that the translation from "$id":OrderId("someid") to "$id":"someid" is not what should happen.
EDITED:
I ended up asking the vendor to change the implementation of sending the JSON
I want to parse a JSON string into a Dictionary using swift.
The plist key has the following value:
"{runid:\"8090\",status_id:\"5\"}"
and when I convert this into a String object, it looks like this "\"{runid:\\\"8488\\\",testids:[\"7480769\"]}\""
Code
let data = theString.data(using: .utf8)
let jsonObject = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments)
I have already gone through various posts and am not able to use the conventional solution we use in our daily lives.
Following things I already know:
The keys are not properly formatted in quotes
This is ideally not structured in the conventional way for parsing.
NOTE
Important thing to note is that I will not be able to change the format, because this value is coming from a third party vendor.
Another thing to note is that this string is being successfully parsed by the JAVA team in the company
I don't know if it covers all cases, but this is a solution using Regular Expression.
It searches for a pattern
{ or ,
one or more alphanumeric characters
:
and captures the first and second condition. Then it replaces the found match by adding the quotes:
let theString = "{runid:\"8090\",status_id:\"5\"}"
let validJSONString = theString.replacingOccurrences(of: "([{,])(\\w+):", with: "$1\"$2\":", options: .regularExpression)
print(validJSONString)
Blame the third party vendor for that mess. Things won't change if nobody complains.
I'm trying to parse a JSON structure that looks like this:
{"amount": -9.45}
Using JSONSerializer, the amount gets parsed as a floating point number leading to errors in the numbers. This is the code I currently have:
import Foundation
let jsonData = "{\"amount\": -9.45}".data(using: .utf8)!
let jsonObject = try! JSONSerialization.jsonObject(with: jsonData, options: []) as! [String: Any]
jsonObject["amount"] // -9.449999999999999
Is there a way I can get to the underlying string that is being parsed into a floating point number so I can parse it myself?
Notes:
This is financial data so I'd like to parse it as either an Int or Decimal.
I cannot control the source of the data.
I'm trying to avoid pre-processing the data/writing my own JSON parser.
EDIT: After reviewing the code of the Swift version of JSONSerialization provided by Apple as part of the open source Foundation project, I do not believe that it's possible to solve this problem using the stock class. I made a patched version of the class that parses all numbers as Decimal's instead of Int's or Double's and that solved my problem.
I´m using an JSON api to get data and everything is working as it should, but I don´t really like the way I´m handling the result to be safe if it´s nil. This is how it looks today:
I get my JSON and then I iterate it and do the following
let obj = Obj()
obj.name = json["name"].string ?? ""
obj.age = json["age"].string ?? ""
obj.length = json["length"].string ?? ""
So I check if for example json["name"].string has a value otherwise I assign it with an empty "". Is this the way to do it with SwiftyJSON or is there a cleaner way?
The issue is that sometimes json["name"].string has a value but not json["age"].string for example.
What you need to do before you start writing code is to figure out what you actually want to do if a value isn't present, or if it is null. You apparently decided that you replace all values that are absent with empty strings. That means you lose the ability to distinguish between the two cases. At some point that will cost you.
I'd add methods that can be called as
jsonString ("name")
jsonStringOrEmpty ("name")
jsonStringOrEmptyAcceptNull ("name")
and so on, using exactly the one that I want to use in each case, and each implemented to give me debugging information when an unexpected input is encountered.
I'm using Swift to parse JSON strings. This is the code:
var jsonStr = "..." // JSON string
var data = jsonStr.dataUsingEncoding(NSUTF8StringEncoding)
var error: NSError?
var array = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as [AnyObject]
And this code works almost all the time. But today I tried to parse a new string input but it crashed my app in the last line. I couldn't even see the error message in the "error" variable... It crashed before that variable as updated with the error info.
The JSON string that I'm trying to parse is here: http://pastebin.com/wf6jtNhf
I'm confident that my JSON string is valid for two reasons:
I validated the input using jsonlint.com
I created a Objective-C version of the code above and it successfully parsed the same input
Can anybody see a reason why I can't parse this string or should I assume that the NSJSONSerialization class is bugged in Swift? I'm using Xcode Beta 3.
Edit 1:
Apparently there is a lot of UTF-16 characters in my string (emoji characters). Is there a key to parse the string keeping those characters? I tried NSUTF16StringEncoding the code below, but it didn't work:
var data = jsonStr.dataUsingEncoding(NSUTF16StringEncoding)
Edit 2:
I posted this same question in the Apple Developer forum and apparently there is indeed a bug in the Swift version of NSJSONSerialization.JSONObjectWithData() when there are emoji characters in the data. I hope this gets fixed in the final version.
Also, changing my variable from [AnyObject] to as? Dictionary, as some suggested below, didn't crash my app anymore.
Testing this on my computer, you're crashing because you're casting as [AnyObject], and you're getting a nil result back out. If you change that to as? [AnyObject] then it will allow the return value to be nullable and you'll then be able to print the error.
As for the reason it's failing to parse, I'm not quite sure yet. The culprit seems to be the following chunk, not sure why it's invalid, perhaps some of the UTF is giving the parsing code problems.
"id": "#babiminkah",
"nome": "B\u00E1rbara Santos",
"imagem": "http://pbs.twimg.com/profile_images/490247572175327233/w4dXqfPm_bigger.jpeg",
"texto": "amanh\u00E3 tem b\u00F3 do catarina..... na friends \uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02\uD83D\uDE02\uDE02",
"horario": "2014-07-18 17:43:04"
}
Edit1: Ripping out more pieces of that chunk, it is the texto field's value that's causing the problem. If you remove all of the value after and including \uD83D it will work. These unicode values seem to be the problem.
Edit2: According to this, http://www.fileformat.info/info/unicode/char/d83d/index.htm , D83D is not a valid unicode character. It seems likely some of the subsequent ones are not either.