Let's say there's a JSON-string I got from server: "\"3\"" (with quotes, i.e. length == 3 here)
In Android-world, I can do:
gson.fromJson(json, new TypeToken<String>() {}.getType()); - it returns "3" (i.e. length == 1)
In C#-world, can use NewtonSoft.Json:
JsonConvert.DeserializeObject<string>(json, settings) - it returns "3" (i.e. length == 1)
And other way around, I do have string I want to serialize as a JSON.
In Android I'd do gson.toJson("\"3\"") and in C# - JsonConvert.SerializeObject("\"3\"")
The problem with JSONSerialization is that it doesn't treat plain string as a valid JSON: JSONSerialization.isValidJSONObject("\"3\"") == *false*
What would be equivalent in Swift / Obj-C world?
The ugly workaround I've found (except of just adding/removing quotes) so far is to wrap string into 1-item-array to make JSONSerialization happy and then remove "[","]" from resulted JSON-string (and other way around - add "[", "]" before deserialization), but it's a way too disgusting to be the real solution for this problem.
When de-serializing JSON which does not have an array or
dictionary as top-level object you can pass the
.allowFragments option:
let jsonString = "\"3\""
let jsonData = jsonString.data(using: .utf8)!
let json = try! JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
if let str = json as? String {
print(str) // 3
}
However, there seems to be no way to serialize a plain string to JSON
with the JSONSerialization class from the Foundation library.
Note that according to the JSON specification,
a JSON object is a collection of name/value pairs (dictionary) or
an ordered list of values (array). A single string is not a valid
JSON object.
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.
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...
I am trying to send a JSON to websocket. the required format is coming up with commas whereas when i add item to dictionary then its creating a semi-colon.
Required Format :
{"key":"driver_location_updates","driverID":40,"coordinates":[25.22632,55.2844576]}
Format I created :
"driver_location_updates" =
{
coordinates = ( "24.96046731716484","67.05977029173361");
driverID = 16;
key = "driver_location_updates";
};
}
As you said in comments
key is not with inverted commas.
There is Semi-colon after end of every value
Round Bracket in coordinates
Explanation :
Because your key is single worded so it is assumable that it is string (as there is chars not integer). Try by keeping key as two words like key mine or key_2
Output =>
Because after every key in dictionary there is semi-colon. (x-code syntax for dictionanary).
Because array in console is represented in (...) where as dictionary will be represented in {...}.
Now, more over if you observe there is = in Dictionary but in json there is :. It is just because array dictionary notation is different from json.
By considering the above points, it makes you clear that both are same.
You should use JSONSerialization to convert dictionary to json I guess.
let dictionary: [String: Any] = ["key":"driver_location_updates", "driverID": 40, "coordinates": [25.22632,55.2844576]]
let jsonData = try? JSONSerialization.data(withJSONObject: dictionary, options: [])
let jsonString = String(data: jsonData!, encoding: .utf8)
print(jsonString)
I hope you know how to convert the answer to Objective C
From the format I guess you have used [NSString stringWithFormat:#"%#"] or got the "JSON" from a call to -[NSDictionary description].
This does not create JSON, but a general, human readable notation of the data structure. It kind of looks like JSON and I had the exact same issue many years back :)
Use NSJSONSerialization to get real JSON:
NSData *JSONData = [NSJSONSerialization dataWithJSONObject:dictionary options:0 &error];
or directly write to a stream
[NSJSONSerialization writeJSONObject:dictionary toStream:writeStream options:0 error:&error]
I am using JSONSerialization quite often in my project.
Here is an example of my JSONSerialization code:
let json = try JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any]
Note: Options are missing for purpose and I normally have them in my project.
My problem is that I am not pretty sure what do these options: [] do?
What I have found about options:
NSJSONReadingMutableContainers:
Specifies that arrays and dictionaries are created as mutable objects.
NSJSONReadingMutableLeaves:
Specifies that leaf strings in the JSON object graph are created as
instances of NSMutableString.
NSJSONReadingAllowFragments:
Specifies that the parser should allow top-level objects that are not
an instance of NSArray or NSDictionary.
Note2: I found those definitions on : https://developer.apple.com/reference/foundation/nsjsonreadingoptions
My question is:
Can someone please explain me differences between those options , what should I use them for and if you could show me some code example of those options it would be perfect :).
Any help appreciated.
Thanks.
Short answer for the first two options:
Ignore them in Swift!
In Swift you can make objects mutable just with the var keyword.
In Objective-C on the other hand you need
NSJSONReadingMutableContainers to make the nested collection types mutable NSArray → NSMutableArray and NSDictionary → NSMutableDictionary.
NSJSONReadingMutableLeaves to make the value strings mutable → NSMutableString.
In both Objective-C and Swift if you are only reading the JSON you don't need mutability at all.
The third option NSJSONReadingAllowFragments is important if the root object of the received JSON is not an array and not a dictionary.
If it is an array or dictionary you can omit that option, too.
The pair of empty brackets [] represents No options (the options parameter can be omitted in Swift 3+).
You'd better know how JSON values are imported into iOS world:
JSON array -> NSArray
JSON object -> NSDictionary
JSON number -> NSNumber
JSON string -> NSString
JSON true -> NSNumber
JSON false -> NSNumber
JSON null -> NSNull
(You'd better also check the RFCs of JSON. RFC-4627, RFC-7159)
Then re-check the all options again:
mutableContainers (NSJSONReadingMutableContainers):
Guarantees the NSArrays or NSDictionarys contained in the result must be NSMutableArrays or NSMutableDictionarys. Someone says in older iOSs JSONSerialization (NSJSONSerialization) returned mutable objects without specifying mutableContainers, but depending on it is not recommended, and actually you can find someones reporting such code does not work in iOS 10.
In Swift, mutability is represented by var and let, so you have no need to use this option in Swifty codes. Only needed when you cast some parts of the deserialized result to NSMutableArray or NSMutableDictionary. I strongly recommend to rewrite such codes in a more Swifty manner.
mutableLeaves (NSJSONReadingMutableLeaves):
Guarantees the NSStrings contained in the result must be NSMutableStrings. Rarely used even in old Objective-C codes, ignore it.
allowFragments (NSJSONReadingAllowFragments):
In old RFC (RFC-4627), only array and object were valid as the outermost component of JSON. If you expect array or object (NSDictionary) from the server, NOT specifying this option would help you to find the invalid returned value from the server a little bit sooner.
Seeing the difference in codes:
Assume data1 is a valid UTF-8 representation of the following JSON:
[{"name": "aaa", "value": 123}, {"name": "bbb", "value": 456}]
And the code:
do {
let result = try JSONSerialization.jsonObject(with: data1)
let resultArray = result as! NSMutableArray //->This may cause your app crash
//->Could not cast value of type '__NSArrayI' (0x105e79c08) to 'NSMutableArray' (0x105e79cd0).
print(resultArray)
} catch {
print(error)
}
do {
let result = try JSONSerialization.jsonObject(with: data1, options: [.mutableContainers])
let resultArray = result as! NSMutableArray //->This should always work
print(resultArray) //->shows output...
} catch {
print(error)
}
And data2:
-1
And the comparison for it:
do {
let result = try JSONSerialization.jsonObject(with: data2)
print(result)
} catch {
print(error) //->Error Domain=NSCocoaErrorDomain Code=3840 "JSON text did not start with array or object and option to allow fragments not set." UserInfo={NSDebugDescription=JSON text did not start with array or object and option to allow fragments not set.}
}
do {
let result = try JSONSerialization.jsonObject(with: data2, options: [.allowFragments])
print(result) //-> -1
} catch {
print(error)
}
Options: [] is an empty array returns nothing.
Whereas Options: [] can also be amend with:
NSJSONWritingOptions: for writing JSON data like.
NSJSONWritingOptions.NSJSONWritingPrettyPrinted: Specifies that the JSON data should be generated with whitespace designed to make the output more readable. If this option is not set, the most compact possible JSON representation is generated.
NSJSONReadingOptions: used when creating Foundation objects from JSON data.
NSJSONReadingOptions.MutableContainers: Specifies that arrays and dictionaries are created as mutable objects.
.mutableLeaves: Specifies that leaf strings in the JSON object graph are created as instances of NSMutableString.
.allowFragments: Specifies that the parser should allow top-level objects that are not an instance of NSArray or NSDictionary.
I have JSONString from api as bellow:
[JSONString from api]
But after I read in iOS from Alamofire the order of the JSONString is not correct as api:
[JSON After read in iOS]
How can I keep the JSON format the same order as api?
As explained by #Nimit, JSON format represented in your callback and the API response is of least concern. What you need to care about is that when you are accessing the values from the response, the KEY should be same as seen in the API. No mismatch, not even of the case-sensitive letter, or you will always get the NIL in the response.
To explain it better to you with the use of Alamofire, let's me show you one example:
let APIURL = "https://api.yoururl.com"
Alamofire.request(.GET, APIURL , headers: headers) .responseJSON { response in
let value = response.result.value!
let JSONRes = JSON(value)
let KLValue = JSONRes["Kuala Lumpur"].int!
print(KLValue) //Or call a function to handle the callback
}
Here I am using SwiftyJSON for JSON. In the end, all you want to do is get the data out of the associated keys in the JSON response, no need to worry about how they have been formatted, or what's the order of Keys in the response - most of the time you will get the same as in the API - but in case it changes, need not to worry.
On the another front, to be sure that nothing happens to your app when JSON fields are nil, always put an if-let like this:
if let valueFromJSON = JSONRes["Kuala Lumpur"].string {
someVariable = valueFromJSON
} else {
someVariable = "No Value"
}
Thanks!
You can't do it, unless you write your own JSON parser. Any self-respecting JSON library won't guarantee you the order, if it wants to conform to the JSON spec.
From the definition of the JSON object:
the NSDictionary class represents an unordered collection of objects;
however, they associate each value with a key, which acts like a label
for the value. This is useful for modeling relationships between pairs
of objects.
If you have a jsonObject, such as data, then you can convert to json string like this:
let jsonString = JSONSerialization.data(withJSONObject: data,
options: JSONSerialization.WritingOptions.sortedKeys)
when you use sortedKeys option, the json will be specifies that the output sorts keys in lexicographic order.