I have encoded a Codable object:
let encodedData = try JSONEncoder().encode(someObject)
And I print the JSON by doing the following (I know its not safe, I'm just testing):
let json = try! JSONSerialization.jsonObject(with: encodedData)
print("JSON: \(json)")
My JSON has lots of spaces. I want a way to remove these spaces, to save space on the encoded String. You can tell that it looks quite different from a normal JSON due to these spaces.
JSON (part of it):
How can I reduce the spaces to reduce the bytes this takes up?
As #Martin R pointed out, I was not printing the JSON properly. It should have instead been:
let jsonString = String(data: encodedData, encoding: .utf8)!
print(jsonString)
The result looks like so:
{"type":1,"modifiers":[],"parameters":{ ...
This printed data can then be decoded in the future like so:
let data = Data(jsonString.utf8)
let someResult = try JSONDecoder().decode(SomeType.self, from: data)
Related
I get a response from an API (unfortunately, I cannot change it) that looks something like (just an example):
As bytes => "{\"key\":\"value\"}"
The starting and ending quotes and the escaped quotes are all part of the response, I am solving it in a really ugly way that looks like this:
// (...) Receiving response
guard var responseString = String(bytes: data, encoding: .utf8) else {
print("Response wasn't just a string, great!") // unfortunately, this never happens
return
}
responseString = responseString.trimmingCharacters(in: .whitespacesAndNewlines) // make sure it is trimmed
responseString = String(responseString.dropFirst()) // drop the quote at the start
responseString = String(responseString.dropLast()) // drop the quote at the end
responseString = responseString.replacingOccurrences(of: "\\\"", with: "\"")// convert all \" to " (and hope nothing else is escaped <<< this is really bad!!!)
let responseDataToDecode = responseString.data(using: .utf8)!
// (...) decoding with JSONDecoder
Is there a way to automatically unescape the string and use the JSON object that is contained in it?
If it's double-encoded, then you just need to double-decode. If I understand correctly, the incoming data is like this:
let str = #""{\"key\":\"value\"}""#
// "{\\"key\\":\\"value\\"}"
The first byte is ", the second byte is {, the third byte is \, the fourth byte is ".
That's a JSON-encoded string. So decode that as a string (there was a time when this didn't work because it's a "fragment," but it works fine currently, at least in all my tests):
let decoder = JSONDecoder()
let string = try! decoder.decode(String.self, from: Data(str.utf8)) // {"key":"value"}
And then decode that as your type ([String:String] just for example):
let result = try! decoder.decode([String:String].self, from: Data(string.utf8))
// ["key": "value"]
(IMO this kind of double-encoding is fine, BTW, and I'm not sure why there are so many comments against it. Serializing an arbitrary object makes a lot more sense in many cases than forcing the schema to deal with an arbitrary structure. As long as it's cleanly encoded, I don't see any problem here.)
There's a first step: You need an official documented statement what exactly the format of your data is. It looks like someone took some data, turned it into JSON data, interpreted the data as a string, and then converted the string to a JSON fragment. It's not difficult to decode the JSON fragment, getting a string, turning the string into data, and decoding that data as JSON (starting with JSONSerialization and .allowFragments, probably the only time you should use .allowFragments in your life). Doing it without swearing is hard.
But first you want in writing that this is actually the format. Because I would bet that whoever is responsible for that data format will eventually change it without telling you and break your code.
I'm attempting to print out a string array without escaped single quotes. For some reason, Swift is injecting escaped single quotes when printing my array. This has a trickle down problem when I use the array to build JSON. JSON ends up not being able to parse due to the escaped single quotes.
I thought this was a problem with my code, but I've distilled this down to a single usecase that should be straightforward.
let names = ["Tommy 'Tiny' Lister", "Tom Hanks"]
print(names)
The output is:
["Tommy \'Tiny\' Lister", "Tom Hanks"]
Note: I did not include escaped single quotes in my names array.
How do I prevent this from happening?
Here is the what I'm doing later in code to create JSON. For purposes of brevity, this is a really dumbed down version of what I'm doing:
var names = ["Tommy Tiny Lister", "Tom Hanks"]
var jsonString = """
{"names": \(names)}
"""
var jsonData = jsonString.data(using: .utf8)
if let json = try? JSONSerialization.jsonObject(with: jsonData!) as? [String: Any] {
let jsonData = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
let string = String(data: jsonData, encoding: .utf8)
print(string!)
}
What you are doing is using Swift arrays' description to generate JSON:
var jsonString = """
{"names": \(names)}
""" // your JSON will contain name.description
You are relying on the fact that the implementation of description just so happens to result in the same format as JSON most of the time. As you can see, when there are ' in the array elements, the description is not valid JSON.
Basically, you should not rely on the description to generate JSON. Instead, you should use Codable to generate JSON data from a Swift array.
For the JSON you want to produce:
{
"names": ["Tommy 'Tiny' Lister", "Tom Hanks"]
}
You can use a struct like this:
struct Names : Codable {
let names: [String]
}
And then you can produce JSON Data like this:
let encoder = JSONEncoder()
do {
let obj = Names(names: ["Tommy 'Tiny' Lister", "Tom Hanks"])
let data = try encoder.encode(obj)
} catch { ... }
I am working with swift Alamofire, but i had an issue, i want pass data to the server but the server required escaped JSON string, like this
{\"status\":1,\"id\":\"1bcc3331b09d32f7439ad9d5f2acfb35\",\"progress\":[{\"airline_code\":\"SRI\",\"ctr\":0,\"rate\":5,\"state\":1,\"data\":null}]}
but I have this data from the response:
{"status":1,"id":"1bcc3331b09d32f7439ad9d5f2acfb35","progress":[{"airline_code":"SRI","ctr":0,"rate":5,"state":1,"data":null}]}
how can I convert JSON to escaped JSON like that,
I have tried this online converter
Converter Escape JSON
and its works perfect, how can I escape like that in swift Xcode.
update complete json (look inside feed: )
{
"flight_child":"0",
"child":"[]",
"depart_hidden_transit":"0",
"depart_adult_surcharge":"0",
"depart_flight":"[{\"airlineCode\":\"LIO\",\"arriveCity\":\"Jakarta Soekarno Hatta\",\"arriveDate\":\"2018-04-28\",\"arriveDatetime\":\"2018-04-28 20:05\",\"arrivePort\":\"CGK\",\"arriveTime\":\"20:05\",\"arriveTimezone\":7.0,\"departCity\":\"Yogyakarta\",\"departDate\":\"2018-04-28\",\"departDatetime\":\"2018-04-28 18:50\",\"departPort\":\"JOG\",\"departTime\":\"18:50\",\"departTimezone\":7.0,\"flightNumber\":\"JT 555\",\"stopCount\":0}]",
"auth_mode":"",
"flight_infant":"0",
"flight_to":"CGK",
"flight_return":"",
"depart_child_discount":"0",
"infant":"[]",
"depart_choice":"d3ea46551c769f462c3e1a4dd25c933d",
"depart_child_surcharge":"0",
"version_code":"3",
"contact_email":"mul#gmai.oc",
"contact_name":"Mulia RIfai",
"adult":"[[\"Mr\",\"Muhammad Fuad\",null,null,\"0\",null,null,null,null,null]]",
"flight_trip":"oneway",
"flight_adult":"1",
"depart_adult_discount":"0",
"contact_phone":"0972312",
"depart_carrier":"LIO",
"client_password":"arena123",
"depart_infant_discount":"0",
"depart_infant_surcharge":"0",
"client_username":"androidarena",
"flight_depart":"2018-05-03",
"feed":"{\"status\":1,\"id\":\"1bcc3331b09d32f7439ad9d5f2acfb35\",\"progress\":[{\"airline_code\":\"SRI\",\"ctr\":0,\"rate\":5,\"state\":1,\"data\":null}]}",
"contact_title":"Mr.",
"flight_from":"JOG",
"depart_class":"eco",
"device_id":"123456789011123"
}
Even though this should be fixed by the server. A nasty hack would be to first stringify your json that should be passed as the string and then replacing the occurrence of double quotation with \".
let dic = ["status":1,
"id":"1bcc3331b09d32f7439ad9d5f2acfb35",
"progress":[["airline_code":"SRI","ctr":0,"rate":5,"state":1,"data":nil]
]
] as [String : Any]
if let json = try? JSONSerialization.data(withJSONObject: dic, options: JSONSerialization.WritingOptions.init(rawValue: 0)) {
if let str = String(data: json, encoding: String.Encoding.utf8)?.replacingOccurrences(of: "\"", with: "\\\"") {
print(str)
}
}
Result:
{\"id\":\"1bcc3331b09d32f7439ad9d5f2acfb35\",\"status\":1,\"progress\":[{\"state\":1,\"airline_code\":\"SRI\",\"data\":null,\"ctr\":0,\"rate\":5}]}
I'm trying to perform a HTTP request to a server: the content is a JSON object, which contains a numeric value for the key "amount". If the "amount" is a value with a decimal digit, e.g. 1.6, the request will contain the value
1.6000000000000001, and this value is not accepted by the Server (the api is Java made and the type is a float . I cannot send a String to the server, since the API that receives the data from me can only accept numbers for the "amount". I tried to perform the request with Siesta Framework or with dataTask, but the result is always the same
this is how I create the request (I omitted the less important parts)
let jsonData = try! JSONSerialization.data(withJSONObject: jsonObject) // jsonObject contains the Double value "amount"
let request = URLRequest(url: url)
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request)
task.resume()
Without code that fully reproduces the issue, it’s hard to say for sure, but I imagine what you’re seeing is this behavior:
let amount = 1.6 // Double
let jsonObject = ["amount": amount]
let jsonData = try! JSONSerialization.data(withJSONObject: jsonObject)
String(data: jsonData, encoding: String.Encoding.utf8)
Swift Foundation’s JSON serialization always formats numeric values to their full precision — so the double-precision number 1.6 gets formatted as 1.6000000000000001.
Solution 1: Send a string
You can and should send a string if the server accepts it:
let amount = "1.6" // String
let jsonObject = ["amount": amount]
let jsonData = try! JSONSerialization.data(withJSONObject: jsonObject)
String(data: jsonData, encoding: String.Encoding.utf8)
Note that a string is the only correct way to send this value if you are dealing with money or anything else where exact values matter: even if you spell it as 1.6, a standard JSON parser will likely convert it to a floating point on the receiving end.
Solution 2: Use Decimal to alter the formatting
If you just need to format it with less precision to make it pass validation on the server for some reason, you can embed it in the JSON as a Decimal instead of a Double and it will get formatted differently:
let amount = 1.6
let jsonObject = ["amount": Decimal(amount)]
let jsonData = try! JSONSerialization.data(withJSONObject: jsonObject)
String(data: jsonData, encoding: String.Encoding.utf8)
// {"amount":1.6}
You can even manipulate the Decimal to round to a certain precision.
Note, however, that this does not spare you from floating point precision issues: you are still sending a float according to the JSON spec, and it will still most likely be parsed as a float on the receiving end.
Without seeing your code it is challenging to offer help, however, one thing to check is whether you are using NumberFormatter to be sure you are getting the correct rounding on your values. Assuming you are pulling the number from a UITextField then you would need something like this:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
if let textValue = textField.value {
var amount = numberFormatter.number(from: textValue)
}
Take this simplified example:
let dict: [String: AnyObject] = ["foo": ("bar" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!9]
let json = try! NSJSONSerialization.dataWithJSONObject(dict, options: [])
I cannot get that to run, it crashes with:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (NSConcreteMutableData)'
I need to send a block of JSON to my server, where some values are strings and some are NSData. What am I doing wrong?
It's worth your time to read through the JSON RFC. Note:
JSON can represent four primitive types (strings, numbers, booleans, and null) and two structured types (objects and arrays).
JSON can't represent raw data, so you'll need to convert the NSData to one of those other types (typically a string). One common approach is:
let stringFromData = NSString(data: data, encoding: NSUTF8StringEncoding)
Just make sure you and your server agree on which encoding you'll use.
If you have a lot of these, you can map through your dictionary and convert them at once. There are a few possible implementations depending on your structure; here's one approach:
let dictWithString = dict.map { (key, value) -> (String, NSObject) in
if let value = value as? NSData {
return (key, NSString(data: value, encoding: NSUTF8StringEncoding)!)
}
return (key, value)
}
You can also convert your NSData into Base64 Encoded string in order to set to your json content.
let base64String = rawData.base64EncodedStringWithOptions(.Encoding64CharacterLineLength)
and decoding:
let rawData = NSData(base64EncodedString: base64String, options: .IgnoreUnknownCharacters)