What do JSONSerialization options do and how do they change jsonResult? - ios

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.

Related

How to send JSON with dictionary and array in iOS(Objective c )?

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]

Swift NSDictionary get 0th value without knowing key name

I have an NSDictionary that has a key like messageID5 and the value has three key/value pairs.
I know the NSDictionary only has 1 value in it because I limited my query to 1. But I don't know the name of the key. I just want the value, but I can't access it like an array [0]. You can access it just fine in PHP or Python. I've been trying a lot of different solutions for this basic problem, but a lot of them seem overly messy. anyValue[0] gives me a type error.
If you don't know your dictionary keys, you can get your NSDictionary allKeys.first property or allValues.first:
let dict = NSDictionary(dictionary: ["a":["b":1]])
let subDict = dict[dict.allKeys.first] as? [String:Any] ?? [:] // ["b": 1]
// or
let subDict = dict.allValues.first as? [String:Any] ?? [:] // ["b": 1]
The first thing to acknowledge is that key/value pairs in dictionaries does not maintain any specific order - this is required for an optimization in access to the contents of this structure.
As for your case if you're 100% sure you'll have only one value inside your dictionary you can use .allValues.first to retrieve the contained value. If your know that the type of your value is NSDictionary the whole code may look like this:
let childDictionary = rootDictionary.allValues.first as? NSDictionary
I suggest using (dictionary as Dictionary).values.first. That returns an optional, since it can fail if the dictionary is empty.
(Note that I edited this answer to cast the dictionary from an NSDictionary to a Dictionary so you an use the values property. NSDictionary doesn't have a values property, but Dictionary does.)

Convert plain string to JSON and back in Swift

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.

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.

Could not cast value of type '__NSArrayM' to 'NSDictionary'

I have a json.I am trying to parse that with that code.But its says
Could not cast value of type '__NSArrayM' to 'NSDictionary'
do {
let dataDictionary: NSDictionary = try NSJSONSerialization.JSONObjectWithData(responseObject as! NSData, options: NSJSONReadingOptions.MutableContainers) as! NSDictionary // <------ Error
if let customerArray = dataDictionary.valueForKey("cart") as? NSArray {
for js in customerArray {
let nameArray = js.valueForKey("name")
let idArray = js.valueForKey("id")
}
}
}
Thank you for your helps
The root object in your data is an array, not a object (dictionary).
You need to dynamically decide how to handle your JSON depending on the deserialized object.
What it's telling you is that the JSON object that you're parsing is not a dictionary, it's an array. So if you change it so that you treat its value as an array instead of a dictionary, you'll be able to iterate over that.
You need to reevaluate your JSON to ensure that it's structured the way you think it is. It would also be useful if you posted the JSON that you're trying to parse so that we can see it's structure as well.

Resources