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.
Related
I want to wrap data to JSON before sending.
func request(content: [String]) {
try? JSONEncoder().encode(content)
This worked well when sending arrays of string. However, I would also like to send other structures that can be represented in JSON.
For example arrays containing arrays, or dictionaries.
My idea was to make it
func request(content: Any) {
try? JSONEncoder().encode(content)
Which gives Protocol 'Any' as a type cannot conform to 'Encodable'
How can I make the parameter as generic as possible so what can be formed as JSON gets formed as JSON and whatever fails, fails? I do understand I can't JSONify everything but I would like to be able to do it with things that I know of can be represented in JSON form. I found things like this https://stackoverflow.com/a/64471720/2161301 but apparently you can't do that on "normal" functions
You can use AnyCodable in your project.
Then you will be allowed to use [String: AnyEncodable] / [AnyEncodable] etc. in the places where you are not able to use Any while trying to use JSONEncoder api.
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.
First question here so please bear with me on that one.
I'm trying to do a class that will handle http requests.
The server will definitely reply in JSON so - on top of handling requests - I'd like it to turn the JSON data into dictionaries as well.
As of now, I have this but I find it rather inconvenient ...
guard let json = try? JSONSerialization.jsonObject(with:data, options .mutableContainers) as? [String:Any] else { return }
I want this class to be useful in as many situations as possible. There will be situations in which the server will send 4 or 5d json.
My question is the following :
Can't I convert the json into an array, regardless of the depth it will have, php style?
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.
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.