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

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)

Related

Unable to get value from JSON array of dictionaries in swift

json response:
"result": {
"user_images": [
{
"id": 113,
"user_id": "160",
"image": "1617349564.jpg",
"image_title": "33"
},
{
"id": 112,
"user_id": "160",
"image": "1617349541.jpg",
"image_title": "22"
},
{
"id": 111,
"user_id": "160",
"image": "1617349528.jpg",
"image_title": "11"
},
........
code: with this code i am getting response like above means all user_images array coming... but here i need image_title how to get that.. if i run for loop getting error.. pls do help
if let code = ((response.dict?["result"] as? [String : Any])){
let userImages = code["user_images"] as? [String : Any]
}
how to get image_title value from above array of dictionaries
Sol 1
if let code = response.dict?["result"] as? [String : Any] {
if let userImages = code["user_images"] as? [[String : Any]] {
for item in userImages {
print(item["image_title"])
}
}
}
Sol 2
if let code = response.dict?["result"] as? [String : Any] {
do {
let data = try JSONSerialization.data(withJSONObject: code)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let res = try decoder.decode(Result.self, from: data)
let titles = res.userImages.map { $0.imageTitle }
print(titles)
}
catch {
print(error)
}
}
// MARK: - Result
struct Result: Codable {
let userImages: [UserImage]
}
// MARK: - UserImage
struct UserImage: Codable {
let id: Int
let userId, image, imageTitle: String
}
Code from #Sh_Khan
if let code = response.dict?["result"] as? [String : Any] {
//You're using "as?" which means you're casting as an optional type.
//One simple solution to this is here where it's unwrapped using "if let"
if let userImages = code["user_images"] as? [[String : Any]] {
// [String:Any] is a Dictionary
// [[String:Any]] is an Array of Dictionary
for item in userImages {
print(item["image_title"])
}
} else {
// We didn't unwrap this safely at all, do something else.
}
Let's dive into this a little bit. This structure is a JSON Object
{
"id": 113,
"user_id": "160",
"image": "1617349564.jpg",
"image_title": "33"
}
But it's only a JSON object when it stands alone. Adding a key, or in this example user_images makes it a dictionary. Notice that the [ is not wrapped around it. Meaning it's a standalone dictionary. If this was your object, and this alone, your original code would work, but you're dealing with an Array of Dictionaries.
"user_images":
{
"id": 113,
"user_id": "160",
"image": "1617349564.jpg",
"image_title": "33"
}
This line of code essentially means that you're expecting to get back that Array of Dictionary. Bear in mind, each value for the dictionary is not an array value, which is why you don't see something like this [[[String: Any]]] because the data isn't nested like that.
if let userImages = code["user_images"] as? [[String : Any]]
What's this about optionals?
An optional is basically a nil possible value that can be returned. Typically when working with JSON you cannot guarantee that you'll always receive a value for a given key. It's even possible for a key value pair to be completely missing. If that were to happen you'd end up with a crash, because it's not handled. Here are the most common ways to handle Optionals
var someString: String? //This is the optional one
var someOtherString = "Hello, World!" //Non-optional
if let unwrappedString1 = someString {
//This code will never be reached
} else {
//This code will, because it can't be unwrapped.
}
guard let unwrappedString2 = someString else {
//This code block will run
return //Could also be continue, break, or return someValue
}
//The code will never make it here.
print(someOtherString)
Furthermore, you can work with optionals by chain unwrapping them which is a nifty feature.
var someString: String?
var someInt: Int?
var someBool: Bool?
someString = "Hello, World!"
//someString is not nil, but an important distinction to make, if any
//fail, ALL fail.
if let safeString = someString,
let safeInt = someInt,
let safeBool = someBool {
//If the values are unwrapped safely, they will be accessible here.
//In this case, they are nil, so this block will never be hit.
//I make this point because of scope, the guard statement saves you from the
//scoping issue present in if let unwrapping.
print(safeString)
print(safeInt)
print(safeBool)
}
guard let safeString = someString,
let safeInt = someInt,
let safeBool = someBool {
//This will be hit if a value is null
return
}
//However notice the scope is available outside of the guard statement,
//meaning you can safely use the values now without them being contained
//to an if statement. Despite this example, they would never be hit.
print(safeString)
print(safeInt)
print(safeBool)

Access a Dictionary index saved in Swift session

So I basically have a login screen on my app and after a successful login, before performing a segue to the main screen I save the following info in a Dictionary in session. It's something like this (putting static info for the example):
let preferences = UserDefaults.standard
let session_info : [String: String] = [
"user_id": "123",
"name": "John",
"email": "john#doe.com"
]
preferences.set(session_info, forKey: "session")
preferences.synchronize()
So the session data gets saved, if I assign it to a variable and print it, I get an optional dictionary of info in my console.
let session_data = preferences.value(forKey: "session")!
If I do a print(session_data) I get this output:
{
"user_id" = "123";
"name" = "John";
"email" = "john#doe.com";
}
My issue is that I am not sure how to access the keys, because if I treat the assigned variable as a Dictionary it says Type 'Any' has no subscript members. What I use is this:
print(session_data["user_id"])
Is there a way to access this info or do I have to save each variable to a separate session key?
Basically never use value(forKey with UserDefaults.
There is a dedicated API dictionary(forKey which returns [String:Any]? by default. In your case conditionally downcast the dictionary to [String:String].
if let sessionData = preferences.dictionary(forKey: "session") as? [String:String] {
print(sessionData["user_id"]!)
} else {
print("sessionData is not available")
}
And this is not JavaScript. Please name variables lowerCamelCased.
A more sophisticated way is to use a struct and save it with Codable
struct Session : Codable {
let userID, name, email : String
}
let preferences = UserDefaults.standard
let sessionInfo = Session(userID:"123", name: "John", email: "john#doe.com")
let sessionData = try! JSONEncoder().encode(sessionInfo)
preferences.set(sessionData, forKey: "session")
do {
if let sessionData = preferences.data(forKey: "session") {
let sessionInfo = try JSONDecoder().decode(Session.self, from: sessionData)
print(sessionInfo.userID)
}
} catch { print(error) }
You have to parse it as a Dictionary.
guard let session_data = preferences.value(forKey: "session")! as? NSDictionary else {return }
print(session_data["user_id"])
It should works.

Require a dictionary to contain certain values (Swift)

I am creating a model for saving user data to a Firestore database and am initializing it with a dictionary. Depending on what fields I want to update, I put those values in the dictionary so that the user model only contains certain fields and will therefore only update those fields in my database. However, I want to somehow require that certain fields are provided in certain use cases.
* Below example is a very simplified version of what I am trying to do *
For example: if I am saving a new user, I want to make sure that I include a name, a profile image, and a description. But if I simply want to update a field, then I don't want to require that all those fields are included
I am 99% certain I am attacking this the wrong way, so any help is appreciated.
My Current User Model:
struct FirestoreUser {
var id: String
var name: String?
var profileImage: String?
var dictionary: [String: Any]
init(dictionary: [String: Any]) {
self.id = dictionary["id"] as! String
self.name = dictionary["name"] as? String
self.profileImage = dictionary["profileImage"] as? String
self.dictionary = dictionary
}
}
// MARK: Firestore functions
extension FirestoreUser {
typealias Handler = ((Error?) -> Void)?
func saveNewFirestoreUser(then handler: Handler = nil) {
// make sure all necessary variables are set
// if they aren't all set, something went wrong
guard let _ = name, let _ = profileImage else { return }
let firestore = Firestore.firestore()
let ref = firestore.collection("users").document(id)
ref.setData(dictionary) { (error) in
if let error = error {
handler?(error)
}
handler?(nil)
}
}
}
Construct struct with optional value and pass nil as the parameter which you don't want to update.
You can use the below extension to convert struct to dictionary later.
struct FirestoreUser: Codable {
var id: String
var name: String?
var profileImage: String?
}
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
Use -
let test = FirestoreUser(id: "01", name: "Abhinav", profileImage: nil)
print(test.dictionary)
Output -
["name": Abhinav, "id": 01]
In realtime database use updateChildValues instead of setValue if you just want to update the child. I believe there's something equivalent in firestore.
Answer for realtime database:
Update
self.ref.child("users/\(user.uid)/username").setValue(username)
Set new data
let key = ref.child("posts").childByAutoId().key
let post = ["uid": userID,
"author": username,
"title": title,
"body": body]
let childUpdates = ["/posts/\(key)": post,
"/user-posts/\(userID)/\(key)/": post]
ref.updateChildValues(childUpdates)
Answer for firestore:
Just read the documentation for firestore, use updateData instead of addDocument:
let washingtonRef = db.collection("cities").document("DC")
// Set the "capital" field of the city 'DC'
washingtonRef.updateData([
"capital": true
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
print("Document successfully updated")
}
}
Set/Add new data
// Add a new document with a generated id.
var ref: DocumentReference? = nil
ref = db.collection("cities").addDocument(data: [
"name": "Tokyo",
"country": "Japan"
]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
So rule of thumb is to only pass in the field that you need to update instead of the whole collection/dictionary.

Remove nested key from dictionary

Let's say I have a rather complex dictionary, like this one:
let dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
I can access all fields with if let .. blocks optionally casting to something that I can work with, when reading.
However, I am currently writing unit tests where I need to selectively break dictionaries in multiple ways.
But I don't know how to elegantly remove keys from the dictionary.
For example I want to remove the key "japan" in one test, in the next "lat" should be nil.
Here's my current implementation for removing "lat":
if var countries = dict["countries"] as? [String: Any],
var japan = countries["japan"] as? [String: Any],
var capital = japan["capital"] as? [String: Any]
{
capital.removeValue(forKey: "lat")
japan["capital"] = capital
countries["japan"] = japan
dictWithoutLat["countries"] = countries
}
Surely there must be a more elegant way?
Ideally I'd write a test helper that takes a KVC string and has a signature like:
func dictWithoutKeyPath(_ path: String) -> [String: Any]
In the "lat" case I'd call it with dictWithoutKeyPath("countries.japan.capital.lat").
When working with a subscript, if the subscript is get/set and the variable is mutable, then the entire expression is mutable. However, due to the type cast the expression "loses" the mutability. (It's not an l-value anymore).
The shortest way to solve this is by creating a subscript that is get/set and does the conversion for you.
extension Dictionary {
subscript(jsonDict key: Key) -> [String:Any]? {
get {
return self[key] as? [String:Any]
}
set {
self[key] = newValue as? Value
}
}
}
Now you can write the following:
dict[jsonDict: "countries"]?[jsonDict: "japan"]?[jsonDict: "capital"]?["name"] = "berlin"
We liked this question so much that we decided to make a (public) Swift Talk episode about it: mutating untyped dictionaries
I'd to like to follow up on my previous answer with another solution. This one extends Swift's Dictionary type with a new subscript that takes a key path.
I first introduce a new type named KeyPath to represent a key path. It's not strictly necessary, but it makes working with key paths much easier because it lets us wrap the logic of splitting a key path into its components.
import Foundation
/// Represents a key path.
/// Can be initialized with a string of the form "this.is.a.keypath"
///
/// We can't use Swift's #keyPath syntax because it checks at compilet time
/// if the key path exists.
struct KeyPath {
var elements: [String]
var isEmpty: Bool { return elements.isEmpty }
var count: Int { return elements.count }
var path: String {
return elements.joined(separator: ".")
}
func headAndTail() -> (String, KeyPath)? {
guard !isEmpty else { return nil }
var tail = elements
let head = tail.removeFirst()
return (head, KeyPath(elements: tail))
}
}
extension KeyPath {
init(_ string: String) {
elements = string.components(separatedBy: ".")
}
}
extension KeyPath: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.init(value)
}
init(unicodeScalarLiteral value: String) {
self.init(value)
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(value)
}
}
Next I create a dummy protocol named StringProtocol that we later need to constrain our Dictionary extension. Swift 3.0 doesn't yet support extensions on generic types that constrain a generic parameter to a concrete type (such as extension Dictionary where Key == String). Support for this is planned for Swift 4.0, but until then, we need this little workaround:
// We need this because Swift 3.0 doesn't support extension Dictionary where Key == String
protocol StringProtocol {
init(string s: String)
}
extension String: StringProtocol {
init(string s: String) {
self = s
}
}
Now we can write the new subscripts. The implementation for the getter and setter are fairly long, but they should be straightforward: we traverse the key path from beginning to end and then get/set the value at that position:
// We want extension Dictionary where Key == String, but that's not supported yet,
// so work around it with Key: StringProtocol.
extension Dictionary where Key: StringProtocol {
subscript(keyPath keyPath: KeyPath) -> Any? {
get {
guard let (head, remainingKeyPath) = keyPath.headAndTail() else {
return nil
}
let key = Key(string: head)
let value = self[key]
switch remainingKeyPath.isEmpty {
case true:
// Reached the end of the key path
return value
case false:
// Key path has a tail we need to traverse
switch value {
case let nestedDict as [Key: Any]:
// Next nest level is a dictionary
return nestedDict[keyPath: remainingKeyPath]
default:
// Next nest level isn't a dictionary: invalid key path, abort
return nil
}
}
}
set {
guard let (head, remainingKeyPath) = keyPath.headAndTail() else {
return
}
let key = Key(string: head)
// Assign new value if we reached the end of the key path
guard !remainingKeyPath.isEmpty else {
self[key] = newValue as? Value
return
}
let value = self[key]
switch value {
case var nestedDict as [Key: Any]:
// Key path has a tail we need to traverse
nestedDict[keyPath: remainingKeyPath] = newValue
self[key] = nestedDict as? Value
default:
// Invalid keyPath
return
}
}
}
}
And this is how it looks in use:
var dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
dict[keyPath: "countries.japan"] // ["language": "japanese", "capital": ["lat": "35.6895", "name": "tokyo", "lon": "139.6917"]]
dict[keyPath: "countries.someothercountry"] // nil
dict[keyPath: "countries.japan.capital"] // ["lat": "35.6895", "name": "tokyo", "lon": "139.6917"]
dict[keyPath: "countries.japan.capital.name"] // "tokyo"
dict[keyPath: "countries.japan.capital.name"] = "Edo"
dict[keyPath: "countries.japan.capital.name"] // "Edo"
dict[keyPath: "countries.japan.capital"] // ["lat": "35.6895", "name": "Edo", "lon": "139.6917"]
I really like this solution. It's quite a lot of code, but you only have to write it once and I think it looks very nice in use.
You could construct recursive methods (read/write) which visits your given key path by repeatedly attempting conversions of (sub-)dictionary values to [Key: Any] dictionaries themselves. Moreover, allow public access to these methods via a new subscript.
Note that you might have to explicitly import Foundation for access to the components(separatedBy:) method of String (bridged).
extension Dictionary {
subscript(keyPath keyPath: String) -> Any? {
get {
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath)
else { return nil }
return getValue(forKeyPath: keyPath)
}
set {
guard let keyPath = Dictionary.keyPathKeys(forKeyPath: keyPath),
let newValue = newValue else { return }
self.setValue(newValue, forKeyPath: keyPath)
}
}
static private func keyPathKeys(forKeyPath: String) -> [Key]? {
let keys = forKeyPath.components(separatedBy: ".")
.reversed().flatMap({ $0 as? Key })
return keys.isEmpty ? nil : keys
}
// recursively (attempt to) access queried subdictionaries
// (keyPath will never be empty here; the explicit unwrapping is safe)
private func getValue(forKeyPath keyPath: [Key]) -> Any? {
guard let value = self[keyPath.last!] else { return nil }
return keyPath.count == 1 ? value : (value as? [Key: Any])
.flatMap { $0.getValue(forKeyPath: Array(keyPath.dropLast())) }
}
// recursively (attempt to) access the queried subdictionaries to
// finally replace the "inner value", given that the key path is valid
private mutating func setValue(_ value: Any, forKeyPath keyPath: [Key]) {
guard self[keyPath.last!] != nil else { return }
if keyPath.count == 1 {
(value as? Value).map { self[keyPath.last!] = $0 }
}
else if var subDict = self[keyPath.last!] as? [Key: Value] {
subDict.setValue(value, forKeyPath: Array(keyPath.dropLast()))
(subDict as? Value).map { self[keyPath.last!] = $0 }
}
}
}
Example setup
// your example dictionary
var dict: [String: Any] = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
Example usage:
// read value for a given key path
let isNil: Any = "nil"
print(dict[keyPath: "countries.japan.capital.name"] ?? isNil) // tokyo
print(dict[keyPath: "airports"] ?? isNil) // ["germany": ["FRA", "MUC", "HAM", "TXL"]]
print(dict[keyPath: "this.is.not.a.valid.key.path"] ?? isNil) // nil
// write value for a given key path
dict[keyPath: "countries.japan.language"] = "nihongo"
print(dict[keyPath: "countries.japan.language"] ?? isNil) // nihongo
dict[keyPath: "airports.germany"] =
(dict[keyPath: "airports.germany"] as? [Any] ?? []) + ["FOO"]
dict[keyPath: "this.is.not.a.valid.key.path"] = "notAdded"
print(dict)
/* [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lon": "139.6917",
"lat": "35.6895"
],
"language": "nihongo"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL", "FOO"]
]
] */
Note that if a supplied key path does not exist for an assignment (using setter), this will not result in the construction of the equivalent nested dictionary, but simply result in no mutation of the dictionary.
Interesting question. The problem seems to be that Swift's optional chaining mechanism, which is normally capable of mutating nested dictionaries, trips over the necessary type casts from Any to [String:Any]. So while accessing a nested element becomes only unreadable (because of the typecasts):
// E.g. Accessing countries.japan.capital
((dict["countries"] as? [String:Any])?["japan"] as? [String:Any])?["capital"]
… mutating a nested element doesn't even work:
// Want to mutate countries.japan.capital.name.
// The typecasts destroy the mutating optional chaining.
((((dict["countries"] as? [String:Any])?["japan"] as? [String:Any])?["capital"] as? [String:Any])?["name"] as? String) = "Edo"
// Error: Cannot assign to immutable expression
Possible Solution
The idea is to get rid of the untyped dictionary and convert it into a strongly typed structure where each element has the same type. I admit that this is a heavy-handed solution, but it works quite well in the end.
An enum with associated values would work well for our custom type that replaces the untyped dictionary:
enum KeyValueStore {
case dict([String: KeyValueStore])
case array([KeyValueStore])
case string(String)
// Add more cases for Double, Int, etc.
}
The enum has one case for each expected element type. The three cases cover your example, but it could easily be expanded to cover more types.
Next, we define two subscripts, one for keyed access to a dictionary (with strings) and one for indexed access to an array (with integers). The subscripts check if self is a .dict or .array respectively and if so return the value at the given key/index. They return nil if the type doesn't match, e.g. if you tried to access a key of a .string value. The subscripts also have setters. This is key to make chained mutation work:
extension KeyValueStore {
subscript(_ key: String) -> KeyValueStore? {
// If self is a .dict, return the value at key, otherwise return nil.
get {
switch self {
case .dict(let d):
return d[key]
default:
return nil
}
}
// If self is a .dict, mutate the value at key, otherwise ignore.
set {
switch self {
case .dict(var d):
d[key] = newValue
self = .dict(d)
default:
break
}
}
}
subscript(_ index: Int) -> KeyValueStore? {
// If self is an array, return the element at index, otherwise return nil.
get {
switch self {
case .array(let a):
return a[index]
default:
return nil
}
}
// If self is an array, mutate the element at index, otherwise return nil.
set {
switch self {
case .array(var a):
if let v = newValue {
a[index] = v
} else {
a.remove(at: index)
}
self = .array(a)
default:
break
}
}
}
}
Lastly, we add some convenience initializers for initializing our type with dictionary, array or string literals. These are not strictly necessary, but make working with the type easier:
extension KeyValueStore: ExpressibleByDictionaryLiteral {
init(dictionaryLiteral elements: (String, KeyValueStore)...) {
var dict: [String: KeyValueStore] = [:]
for (key, value) in elements {
dict[key] = value
}
self = .dict(dict)
}
}
extension KeyValueStore: ExpressibleByArrayLiteral {
init(arrayLiteral elements: KeyValueStore...) {
self = .array(elements)
}
}
extension KeyValueStore: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self = .string(value)
}
init(extendedGraphemeClusterLiteral value: String) {
self = .string(value)
}
init(unicodeScalarLiteral value: String) {
self = .string(value)
}
}
And here's the example:
var keyValueStore: KeyValueStore = [
"countries": [
"japan": [
"capital": [
"name": "tokyo",
"lat": "35.6895",
"lon": "139.6917"
],
"language": "japanese"
]
],
"airports": [
"germany": ["FRA", "MUC", "HAM", "TXL"]
]
]
// Now optional chaining works:
keyValueStore["countries"]?["japan"]?["capital"]?["name"] // .some(.string("tokyo"))
keyValueStore["countries"]?["japan"]?["capital"]?["name"] = "Edo"
keyValueStore["countries"]?["japan"]?["capital"]?["name"] // .some(.string("Edo"))
keyValueStore["airports"]?["germany"]?[1] // .some(.string("MUC"))
keyValueStore["airports"]?["germany"]?[1] = "BER"
keyValueStore["airports"]?["germany"]?[1] // .some(.string("BER"))
// Remove value from array by assigning nil. I'm not sure if this makes sense.
keyValueStore["airports"]?["germany"]?[1] = nil
keyValueStore["airports"]?["germany"] // .some(array([.string("FRA"), .string("HAM"), .string("TXL")]))
Pass your dictionary to this function, It will return you a flat dictionary, without any nested dict incorporated .
//SWIFT 3.0
func concatDict( dict: [String: Any])-> [String: Any]{
var dict = dict
for (parentKey, parentValue) in dict{
if let insideDict = parentValue as? [String: Any]{
let keys = insideDict.keys.map{
return parentKey + $0
}
for (key, value) in zip(keys, insideDict.values) {
dict[key] = value
}
dict[parentKey] = nil
dict = concatDict(dict: dict)
}
}
return dict
}

Creating REST parameters dictionary that may have nil values

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.

Resources