Creating REST parameters dictionary that may have nil values - ios

I'm trying to create a dictionary for my REST params. The problem I am having is that my data model uses many optional values, and my params dictionary of course cannot take nil values. What I would like to do is set NSNull() for values that may be nil. For example, this is how I am creating my params.
func addBook(book : Book, completionHandler: (error: NSError!) -> Void) {
let params : [String: AnyObject] = ["title": book.title!, "author": book.author!]
Alamofire.request(.POST, booksEndpoint, parameters: params, encoding: .JSON)
.responseJSON { response in
...
...
Here, I am implicitly unwrapping my variables, which causes crashes when the object is nil.
Here is how I am initializing my data model
class Book: NSObject {
var title : String?
var author : String?
//Create a custom initializer from parsed JSON object
init (book : NSDictionary)
{
if let author = book["author"] as? String {
self.author = author
}
if let title = book["title"] as? String {
self.title = title
}
}
}
How should I properly handle this situation in which my params may be nil and I need to set key values to NSNull()? In a realistic scenario I will have a larger amount of parameters with possible nil values.

Instead on unwrapping, use the ?? (nil coalescing) operator.
let params : [String: AnyObject] = ["title": book.title ?? NSNull(), "author": book.author ?? NSNull()]
or simply
var params: [String: AnyObject] = [:]
params["title"] = book.title
params["author"] = book.author
which won't assign at all if the value of the right side is nil.
There is one difference, of course. The first method will produce null values in your JSON, the second method will silently omit all null values.

Related

Adding Key and Value to a Dictionary in Swift Based on Condition

I have the following code and I want to add the likes and retweets, only if the likes are not nil. I came up with the following code but was wondering if there is a better way.
func toDictionary() -> [String: Any] {
var tweetDict = ["userId": userId, "text": text, "dateCreated": dateCreated, "dateUpdated": dateUpdated] as [String : Any]
if let likes {
tweetDict["likes"] = likes
}
if let retweets {
tweetDict["retweets"] = retweets
}
return tweetDict
}
I can initialize likes and retweets to be an empty array but then when I save it in Firebase it create an empty array in Firestore database. I think that extra key in Firebase will take up little space even though it is empty (unless my understanding is wrong) and I am not sure if storing empty array in Firebase is a good idea.
Simplest I can think of is add an extension to dictionary:
extension Dictionary {
mutating func updateValueIfNotNil(_ value: Value?, forKey: Key) {
guard let value = value else {
return
}
updateValue(value, forKey: forKey)
}
}
If the provided value is nil, it's ignored, otherwise normal updateValue is performed (which is the same as assigning a value):
var tweetDict = ["userId": "aa", "text": "bb", "dateCreated": Date(), "dateUpdated": Date()] as [String : Any]
let notNil = "something"
let isNil: String? = nil
tweetDict.updateValueIfNotNil(notNil, forKey: "retweets")
tweetDict.updateValueIfNotNil(isNil, forKey: "likes")
print(tweetDict)
would print
["userId": "aa", "text": "bb", "dateCreated": 2022-07-13 20:13:46 +0000, "dateUpdated": 2022-07-13 20:13:46 +0000, "retweets": "something"]
(i.e. "likes" were not added, since their value is nil)

How can I safely unwrap deeply nested values in a dictionary in Swift?

I understand how to print the values of upper level things such as the value of "email" or the value of "name" but how would I safely unwrap the dictionary to print a deeper nested value such as the value of "url"?
Nesting just means that the value for a particular key in your top level [String: Any] dictionary is another [String: Any] - so you just need to cast it to access nested objects.
// assuming you have an object `json` of `[String: Any]`
if let pictureJSON = json["picture"] as? [String: Any] {
// if the JSON is correct, this will have a string value. If not, this will be nil
let nestedURL = pictureJSON["url"] as? String
}
I feel I should mention that the process of serializing/de-serializing JSON in Swift took a big leap with Codable in Swift 4. If you have a model object you want to map this JSON to, this whole thing can be automated away (including nested objects - you just supply a nested Swift struct/class conforming to Codable). Here's some Apple documentation on it.
Like that:
if let picture = dictionary["picture"] as? [String:AnyObject] {
if let url = picture["url"] as? String {
}
if let width = picture["width"] as? Int {
}
}

Type '(String, AnyObject)' has no subscript members in swift

I am using this line to get data
let productDict = arrProductCart[sender.tag] as! [String: AnyObject]
and I want to filter data from dictionary so i am using this code
let filteredSubitems = productDict.filter{
$0["groupid"] as!String != "1"
}
it is giving me error Type '(String, AnyObject)' has no subscript members
do i need to convert [String: AnyObject] to [String: String]?
what to do.
Most probably you want to filter arrProductCard array instead of productDict, which doesn't make sense. Try this:
let filteredProducts = arrProductCard.filter{
guard let groupId = $0["groupid"] as? String else { return false }
return groupId != "1"
}
You should avoid forced unwrapping whenever you can. Note that your code inside filter closure will crash if there is no groupid value in the dictionary or if it is not a string.
Edit:
If you're using NSMutableArray for some reason, you can just filter it with a predicate:
let mutableArray = NSMutableArray(array: [["groupid": "1"], ["groupid": "2"]])
let groupIdPredicate = NSPredicate(format: "groupid != %#", "1")
mutableArray.filter(using: groupIdPredicate)
However, I would strongly recommend to use Swift native collections.

Extract value from dictionary of annoying format

I apologise for the title of this question. I have no idea what else to call it.
So... When calling the following:
let testData: [NSObject : AnyObject] = getTestData()
print(testData)
I get this output:
[data: {"TypeId":7,"DataList":null,"TypeName":"This is a test"}]
How would I be able to access the value 7 for the key "TypeId"?
EDIT:
Please note that it's holding { } brackets, not only [ ], thus a cast to NSDictionary is not possible as far as I have tried.
Kind regards,
Anders
You can achieve plist-like nested structures using Any type for dictionary values which is Swift's somewhat counterpart to Objective-C's id type but can also hold value types.
var response = Dictionary()
response["user"] = ["Login": "Power Ranger", "Password": "Mighty Morfin'"]
response["status"] = 200
Any seems to be better than AnyObject because in the above code response["status"] is of type Swift.Int, while using value type of AnyObject it is __NSCFNumber.
The way most people do it is to parse annoying JSON data as custom objects. That should be done as soon as you get the JSON. Ideally, data as JSON should not be used outside your communication code, example:
First, let's define a class to hold your server data:
class MyServerObject {
let typeId: Int
let typeName: String
let dataList: [AnyObject]?
init(dictionary: Dictionary<String, AnyObject>) {
let dataDictionary = dictionary["data"] as! Dictionary<String, AnyObject>
self.typeId = dataDictionary["TypeId"] as! Int
self.typeName = dataDictionary["TypeName"] as! String
self.dataList = dataDictionary["DataList"] as? [AnyObject]
}
}
Note that init method is already parsing the JSON. This doesn't have to be done in init, you could also create a static parse method that will return a new instance.
Usage:
// demo data
let jsonString = "{\"data\": {\"TypeId\":7,\"DataList\":null,\"TypeName\":\"This is a test\"}}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let json = try! NSJSONSerialization.JSONObjectWithData(jsonData, options: [])
// parsing
let myServerObject = MyServerObject(dictionary: json as! Dictionary<String, AnyObject>)
// now we can simply read data as properties
print(myServerObject.typeId)
print(myServerObject.typeName)
One of the good thing about this solution is that we can check the JSON format and all the properties are parsed with the correct types.
Parsing can be hierarchical, for example, if your dataList contains complex objects, let's call them DataListItem, your parsing method can parse each item separately and put them into a [DataListItem], e.g.
if let dataListJSON = dataDictionary["DataList"] as? [Dictionary<String, AnyObject>] {
self.dataList = dataListJSON.map({ DataListItem($0) })
}
Also note that when parsing as! will crash the app when the format is invalid. as? will return nil if the types don't match. as? is very useful for types that can be nil because they are parsed as NSNull instances.
taking in account your data ...
print(testData)
/*
[data: {
DataList = null;
TypeId = 7;
TypeName = "This is a test";
}]
*/
// DataList type should be declared somewhere
class DataList {}
// parse data or set default value, if 'key' doesn't exist
if let data = testData["data"] as? [String:AnyObject] {
let dataList = data["DataList"] as? DataList // nil
let typeId = data["TypeId"] as? Int ?? 0 // 7
let typeName = data["TypeName"] as? String ?? "" // This is test
}

How can I create an inline dictionary in Swift while omitting potentially nil optionals as values?

I have some optional properties (a small sample of the actual properties in the class, for brevity):
var assigneeIds: [Int]?
var locationIds: [Int]?
var searchQuery: String?
I need to convert these to JSON. I've been advised by the maintainer of the API to not supply keys with nil values, so my toJson method looks like this:
var result: [String: AnyObject] = [:]
if let assigneeIds = assigneeIds {
result["assignee_ids"] = assigneeIds
}
if let locationIds = locationIds {
result["location_ids"] = locationIds
}
if let searchQuery = searchQuery {
result["search_query"] = searchQuery
}
return result
This doesn't feel very "Swift" - it's quite verbose. Ideally, I'd like it to look similar to the following - with the exception of it not setting the key if the optional has no value. The below code will still set the key, along with an NSNull value which gets serialised to "assignee_ids": null in the resulting JSON. Presumably I can configure the serializer to omit NSNull values, but I've encountered this pattern elsewhere and am interested to see if there's a better way.
return [
"assignee_ids" : self.assigneeIds ?? NSNull(),
"location_ids" : self.locationIds ?? NSNull(),
"search_query" : self.searchQuery ?? NSNull()
]
How can I skip creating key->NSNull entries when building an inline dictionary in this way?
You actually don't need to unwrap the values at all, since the value won't be set in the dictionary when it's nil. Example:
var dict : [String : Any] = [:]
dict["a"] = 1 // dict = ["a" : 1]
dict["b"] = nil // dict = ["a" : 1]
dict["a"] = nil // dict = [:]
You can also CMD-Click on the bracket and look at the definition of the Dictionary subscript:
public subscript (key: Key) -> Value?
this means you get an Optional (whether it exists or not) and you set an Optional (to set it or remove it)
Your code will look like this:
var result: [String: AnyObject] = [:]
result["assignee_ids"] = assigneeIds
result["location_ids"] = locationIds
result["search_query"] = searchQuery
return result
Try this:
return [
"assignee_ids" : self.assigneeIds ?? NSNull(),
"location_ids" : self.locationIds ?? NSNull(),
"search_query" : self.searchQuery ?? NSNull()
].filter { key, val -> Bool in !(val is NSNull) }

Resources