Is there any way to detect that an NSDictionary contains a NSNull value?
Be careful when you put NSDictionary and Swift in the same sentence. NSDictionary is a Foundation (i.e. Objective-C) reference type. Swift has its own native Dictionary, which is a value type (i.e. struct). You can bridge between the two easily, but it takes some CPU time doing so.
Now on to your question:
let dict: [String: Any] = [
"firstName": "John",
"lastName": "Smith",
"age": NSNull()
]
let containsNSNull = dict.contains { $0.1 is NSNull } // true
Related
When I try to serialize json data in swift I get this error
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (__SwiftValue)'
Here is the code that is creating this issue:
request.httpBody = try? JSONSerialization.data(withJSONObject: data, options: [])
my data variable looks something like this:
let data = ["first_name": "John", "last_name": "Riverson", "post_info: userPost(title: "Some title", date_published: "some date")] as [String: Any]
I issue is caused due to the userPost struct, but I am not sure how to fix it.
It seems the result of userPost is not available for JSON.
According to JSONSerialization
A Foundation 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
If you want to make userPost could be accpeted by JSONSerialization, refer to Using JSON with Custom Types, and apply Codable to it.
Your analysis is correct that it's due to the struct.
As per the documentation on JSONSerialization:
Foundation 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.
Ref:
https://developer.apple.com/documentation/foundation/jsonserialization
So you will need to convert the struct to dictionary to insert it here.
Something like:
let userPostDict: [String:Any] = //... your logic e.g. userPost.toDictionary
let data = ["first_name": "John",
"last_name" : "Riverson",
"post_info" : userPostDict]
You could have a toDictionary computed property inside your userPost struct that gives you a [String:Any]
Example:
extension YourStruct {
var toDictionary: [String:Any] {
//make the dictionary & return here
}
}
I am working on a project where I am getting a dictionary of an array which again containt the dictionary
My Json Response is
{
"code": 200,
"status": "OK",
"success": "true",
"message": "success",
"data": {
"vehicletypeData": [
{
"vehicle_type_id": "1",
"vehicle_type": "Any"
},
{
"vehicle_type_id": "11",
"vehicle_type": "Bike"
}
]
}
}
And I am parsing the data like
if response.success {
let resObj = response.responseObject as! Dictionary<String, Any>
let catArray = resObj["data"] as! Dictionary<String,Array<Dictionary<String,Any>>> // Crashes here
let vehicleData = catArray["vehicletypeData"] as! Array<Dictionary<String, Any>>
for vehicle in vehicleData {
self.jobCategories.append(PreJobVehicleData.mj_object(withKeyValues: vehicle))
}
}
I am trying to parse it in my model
Here I am getting an error like
- Could not cast value of type '__NSArrayM' (0x109b82e00) to 'NSDictionary' (0x109b832d8)
Any help will be Thankful.
Don't say
resObj["data"] as! Dictionary<String,Array<Dictionary<String,Any>>>
That's too specific. Just say
resObj["data"] as! [String:Any]
You know that when you get something by key out of that dictionary, it will be an array, but you can literally cross that bridge when you come to it.
The same rule applies to your other casts. Just cast to Swift dictionary or array using the broadest simplest possible type.
(Note that all this will be solved in Swift 4, where you can build a knowledge of the JSON structure right into your fetch.)
As you are parsing the nodes separately anyway avoid to cast to (more than two levels) nested types.
According to the error message the value for key data seems to be an array (Array<Dictionary<String, Any>>)
if response.success {
let resObj = response.responseObject as! Dictionary<String, Any>
let dataArray = resObj["data"] as! Array<Dictionary<String, Any>>
...
That implies however that the JSON output is wrong and I have no idea what's next...
Do you guys can bring some light on the use of dictionaries with UserDefaults ?
What I want to get is to pass a certain dictionary from a view controller to another one using UserDefaults.
var dict = [String : AnyObject?]()
dict = ["Name": "Henri", "Group": "A", "Category": "4"]
UserDefaults.standard.set.(dict, forKey: "MyDict")
Then I try to retrieve the data
let retrieveDict = UserDefaults.standard.dictionary(forKey:"MyDict")
I got a nice nil answer in the console. When I try with int or float types it is fine... Is there anything I am missing to make this dictionary ?
First of all, your code won't compile.
Second, a dictionary with type [String, AnyObject?] make no sense because you'll be not able to set nil in UserDefaults. You should use empty String maybe for that purpose.
The working code could look like that :
var dict = [String : Any]() // or maybe [String : String] if you sure about value type
dict = ["Name": "Henri", "Group": "A", "Category": "4"]
UserDefaults.standard.set(dict, forKey: "MyDict")
let retrieveDict = UserDefaults.standard.dictionary(forKey:"MyDict")
Hope it helps you :)
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.
I'm new to Swift, very experienced in Objective-C.
In my app, I am receiving data from a server and mapping it to an NSMutableDictionary. The reason I am using an NSMutableDictionary is that the values are not consistent coming from the server, it's a mix of strings and numbers. And that appears to break a Swift Dictionary that expects only one type of value.
Sometimes the server is not sending a value that the NSMutableDictionary is expecting and it crashes the app.
In my research, it appears that I have to check every object to see if the value exists in Swift before setting it into the NSMutableDictionary.
This is my current code:
let userDictionary:NSMutableDictionary = [
"name": data.objectForKey("name") as! String,
... // many more values
This crashes if there is no "name" value in the response from the server.
It appears the solution would be:
if let nameValue = data.objectForKey("name") as! String {
//not sure what to do in here since the var is assigned as I need
}
// ... check many more values
let userDictionary:NSMutableDictionary = [
"name": nameValue,
// ... assign many more values that I checked above
This seems like a lot of extra code to check every single value from the server. Is there a simpler solution?
Thank you for your time.
#Matt below. Here is the code in detail (took out some of the values in the userDictionary for brevity). I'm taking data from Facebook, adding additional info and saving it to Firebase:
//check all of the values
var birthdayValue:String? = "";
if let value:String? = data.objectForKey("birthday") as? String {
birthdayValue = value;
}
let userDictionary:NSMutableDictionary = [
"name": data.objectForKey("name") as! String,
"birthday": birthdayValue!,
"email": data.objectForKey("email") as! String,
"firstName": data.objectForKey("first_name") as! String,
"lastName": data.objectForKey("last_name") as! String,
"description": "",
"distance": 50,
"facebookID": data.objectForKey("id") as! String,
"location":[ 37.12314, -122.49182 ], //TODO: Get location
"points" : 0,
"rating" : 1,
"school" : "",
]
//we need to convert the FB profile pic to a base 64 string and add it here
let imagePath:String = "https://graph.facebook.com/\(data.objectForKey("id") as! String)/picture?width=375&height=667"
self.getDataFromUrl(NSURL(string: imagePath)!) { (imageData, response, error) -> Void in
//convert the data to base 64
let imgString:String = self.convertDataToBase64(imageData);
let images:Array<String> = [imgString];
userDictionary.setValue(images, forKey: "profilePics")
//save the user to Firebase
userRef.childByAppendingPath(data.objectForKey("id") as! String).setValue(userDictionary)
self.currentUserID = (data.objectForKey("id")) as! String
}
Swift actually support multiple types in a dictionary. The following is legal.
let arr: [NSObject: AnyObject] = [1: "hello", "a": 19, 2: "Swift"]
And you can store optional object in the dictionary:
let arr: [NSObject: AnyObject?] = [1: "hello", "a": 19, 2: nil]
And yes, you might need to check the existence of the value if you do care about it. Instead of if, I would use guard to make sure you can use the variable later.
guard let nameValue = data.objectForKey("name") as? String else {
return
}
// Now you can safely use nameValue.
In my app, I am receiving data from a server and mapping it to an NSMutableDictionary
There is no need for this at all. The data is coming to you as a dictionary (at least I presume it is; if it weren't, you could hardly be calling objectForKey, it seems to me). So now just proceed to use it. There is nothing to "map".
For example, suppose that data is the result of calling NSJSONSerialization's JSONObjectWithData:options: on an original NSData d. And suppose what you expect this to serialize to is a dictionary. So after you've said
var data = try! NSJSONSerialization.JSONObjectWithData(
d, options:[]) as! [NSObject:AnyObject]
...you are finished: that's a mutable dictionary and you are off to the races.
If your data is a mix of strings and numbers you could perhaps try to store it as AnyObject in the dictionary, e.g. [String: AnyObject]
Then you could just try to save your data like this:
var userDictionary: [String: AnyObject] = [:]
userDictionary["name"] = data.objectForKey("name")
And when you need to access it:
if let name = userDictionary["name"] as? String {
// Do something with name
}
If you don't want to save it as AnyObject I'd suggest you create a struct instead.
as! is a force downcast, which causes an exception if this downcast isn't possible (which obviously is the case if the value is nil). as?, on the other hand, only downcasts where possible, and results in nil otherwise (which sounds like what you want).
let userDictionary:NSMutableDictionary = [
"name": data.objectForKey("name") as? String,
... // many more values
]
This should work.
Edit: Never mind, this would only work if you'd use a native Swift dictionary. It's still useful to know about the difference between as? and as!, though.