Updating a nested value in an NSDictionary - swift

I've initialized a dictionary of type [NSObject: AnyObject] so I can save it into NSUserDefaults.
Here's what it looks like:
var allMetadata: [NSObject: AnyObject] = [
String: [String: String]
// Example: "project30": ["deliverablepath": "hello"]
]
I give deliverablepath a value from the very beginning, and later on I want to update it. I've tried this:
allMetadata[arrayOfProjectIDs[index]]!["deliverablepath"]! = "goodbye"
But I get the error
Operand of postfix '!' should have optional type; type is '(NSObject,
AnyObject)'
I know about updateValue(), but it seems to overwrite adjacent keys in the first nested layer, so it's not working for me.
Any ideas?

Use question optional to avoid "let pyramid"
var allMetadata: [String: [String: String]] = ["a": ["b": "c"]]
allMetadata["a"]?["b"] = "z" // ok!
allMetadata["q"]?["b"] = "d" // nil
UPD:
If you want to cast directly, you should try this:
var allMetadata: [NSObject: AnyObject] = ["a": ["b": "c"]]
if var dict = allMetadata["a"] as? [String: String] {
dict["b"] = "z"
// for dict update, because it's value typed
allMetadata["a"] = dict
}
Mention, that I've written "var", not "let" in condition.

To do this in a safe way, it is best to do this in an if let pyramid as follows:
if let projectId = arrayOfProjectIDs[index] {
if var project = allMetadata[projectId] as? [String:String] {
project["deliverablePath"] = "Goodbye"
}
}
That is not too bad actually.

I want to give an alternative answer here.
I understand the original question is about how to deal with nested arrays and dictionaries, but I think it is worth mentioning that this kind of data model may be better implemented with a more formal API.
For example, how about this:
class Project {
var id: String
var deliverablePath: String
... etc ...
}
class ProjectRepository {
func getProjectWithId(id: String) -> Project? {
...
}
}
Then you can use high level code like:
if let project = repository.getProjectWithId("") {
project.deliverablePath = "Goodbye"
}
Underneath you can still implement this with dictionaries and arrays of course.

Related

How to initialize a case insensitive dictionary using Swift?

My problem is that this code is case-sensitive. If I have "Sam" and "sam", they will be sorted into different keys. Any way that I can think of doing this is by converting the string into all lowercase, but I want it to stay as normal while being sorted without case-sensitivity:
var dict: [String: [String]] = [:]
for string in array {
if (dict[string] != nil) {
dict[string]?.append(string)
}
else {
dict[string] = [string]
}
}
As it is right now my code would result in:
["Sam": ["Sam"], "sam", ["sam"]]
Instead of what I want:
["Sam": ["Sam", "sam"]]
How can I accomplish this?
You can use reduce(into:) method and assign each element capitalized to the result:
let array = ["Sam", "sam", "SAM"]
let dict: [String: [String]] = array.reduce(into: [:]) {
$0[$1.capitalized, default: []].append($1)
}
print(dict) // ["Sam": ["Sam", "sam", "SAM"]]
If you just want to have case insensitive keys and case sensitive values, from given array, the shortest solution could be something like this:
var dict: [String: [String]] = [:]
array.forEach { dict[$0.lowercased(), default: []] += [$0] }

Assigning a value to an optional variable

To set a dictionary value, I am doing like below.
var dic: [String:Any]?
dic = [String:Any]()
dic!["name"] = "my name"
dic!["email"] = "aaa#bbb.com"
dic!["nickname"] = "Grrrrrrrr"
I think the exclamation mark (!) is superfluous.
That is, I'd like to set without '!'.
dic = [String:Any]()
dic["name"] = "my name"
dic["email"] = "aaa#bbb.com"
dic["nickname"] = "Grrrrrrrr"
Is there any way to use in short to set a value to optional variable?
Having read the comments, the solution below could be what you are looking for.
var dic = [String: Any]() //Empty dictionary
func processResultOfAlamofire(_ result: [String: Any]?) {
dic = result ?? [String: Any]()
}
If the result of Alamo is nil, dicwill be empty instead of nil. The ?? operator could be written as:
if result == nil {
dic = [String: Any]()
} else {
dic = result!
}
I think that you should update your code, Don't use direct dictionary in your viewcontroller. set optional variable in your model. and when ever you use those values use it with conditional checking like, if let value = somethingOptional{}
or use guard in your API call function so it will never update or effect any view if you are getting values null from API

How to create nested dictionary elements in Swift?

I want to create a variable which stores this:
["messageCode": API_200, "data": {
activities = (
{
action = 1;
state = 1;
}
);
messages = (
{
body = hi;
// ...
}
);
}, "message": ]
What I have done is this:
var fullDict: Dictionary<String, AnyObject> = [:]
fullDict["messageCode"] = "API_200" as AnyObject
var data: Dictionary<String, AnyObject> = [:]
fullDict ["data"] = data as AnyObject
Is this way is correct and how I can add activities?
I would suggest to go with creating a custom Model:
struct Model {
var messageCode: String
var data: MyData
var message: String
}
struct MyData {
let activities: [Activity]
let messages: [Message]
}
struct Activity {
var action: Int
var state: Int
}
struct Message {
var body: String
// ...
}
Thus you could use it as:
let data = MyData(activities: [Activity(action: 1, state: 1)], messages: [Message(body: "hi")])
let myModel = Model(messageCode: "API_200", data: data, message: "")
However, if you -for some reason- have to declare it as a dictionary, it could be something like this:
let myDict: [String: Any] = [
"messageCode": "API_200",
"data": ["activities": [["action": 1, "state": 1]],
"messages": [["body": "hi"]]
],
"message": ""
]
which means that myDict is a dictionary contains:
messageCode string.
data as nested dictionary, which contains:
activities array of dictionaries (array of [String: Int]).
messages array of dictionaries (array of [String: String]).
message string.
One of the simplest reasons why you should go with the modeling approach is because when it comes to read from myModel, all you have to do is to use the dot . notation. Unlike working with it as a dictionary, you would have to case its values which could be a headache for some point. For instance, let's say that we want to access the first message body in data messages array:
Model:
myModel.data.messages.first?.body
Dictionary:
if let data = myDict["data"] as? [String: [[String: Any]]],
let messages = data["messages"] as? [[String: String]],
let body = messages.first?["body"] {
print(body)
}
Since you explicitly want it as [String:AnyObject]:
var dict: [String:AnyObject] = ["messageCode":"API_200" as AnyObject,
"data": ["activities": [["action":1,
"state":1]],
"messages": [["body":"hi"]]] as AnyObject,
"message": "" as AnyObject]
Basically all the root values should be typecasted as AnyObject
Or the long way:
//Activities is as Array of dictionary with Int values
var activities = [[String:Int]]()
activities.append(["action": 1,
"state": 1])
//Messages is an Array of string
var messages = [[String:String]]()
messages.append(["body" : "hi"])
//Data is dictionary containing activities and messages
var data = [String:Any]()
data["activities"] = activities
data["messages"] = messages
//Finally your base dictionary
var dict = [String:AnyObject]()
dict["messageCode"] = "API_200" as AnyObject
dict["data"] = data as AnyObject
dict["message"] = "" as AnyObject
print(dict)
Parsing this to get your data back will be hell; with all the type casts and all.
Example (lets capture action):
let action = ((dict["data"] as? [String:Any])?["activities"] as? [String:Int])?.first?.value
As you can see you need to typecast at every level. This is the problem with using dictionaries in Swift. Too much cruft.
Sure, you could use a third-party library like SwiftyJSON to reduce the above to:
let action = dict["data"]["activities"][0]["action"]
But do you want a dependency just for something as simple as this?
Instead...
If your structure is defined then create models instead; as Ahmad F's answer suggests. It will be more readable, maintainable and flexible.
...but since you asked, this is how one would do it with pure Dictionary elements.

Swift 3.0 Error: Ambiguous reference to member 'subscript' when setting a Dictionary value

I've got this method below which inserts a value into a dictionary. In Swift 2.x, it worked as is. When switching to Swift 3, I get the "Ambiguous reference to member 'subscript'" error.
Can anyone help me out in understanding why. Thanks
private func GetNames() -> AnyObject? {
var returnList : [NSDictionary] = []
let list = self.Names as! [NSDictionary]
for name in list {
var dict : Dictionary<NSObject, AnyObject> = [:]
dict["id"] = name["id"] // <-- ******Error*******
dict["name"] = name["name"] // <-- ******Error*******
returnList.append(dict as NSDictionary)
}
return returnList as AnyObject
}
In Swift 3 String is type of Structure, so it will not work with NSObject and you have declare dictionary thats key type is NSObject that is the reason you are getting that error. To solve the error simply change type of dictionary to [String:Any] form [NSObject:AnyObject] and you all set to go.
var dict : [String:Any] = [:]
Note: Also in Swift don't use NSDictionary and NSArray use Swift's native type dictionary and array.
The error occurs because Swift 3 needs to know all types which are subscripted by key or index.
Using Foundation collection types is counterproductive because they lack exactly that type information the compiler needs.
Returning an optional AnyObject rather than the distinct (non-optional) [[String:Any]] is counterproductive, too.
The solution is to use only (as) specific (as possible) native Swift types.
private func getNames() -> [[String:Any]] {
var returnList = [[String:Any]]()
let list = self.Names as! [[String:Any]]
for name in list {
var dict = [String:Any]()
dict["id"] = name["id"]
dict["name"] = name["name"]
returnList.append(dict)
}
return returnList
}
or swiftier if it's guaranteed that both keys name and id always exist
private func getNames() -> [[String:Any]] {
let list = Names as! [[String:Any]]
return list.map { ["id" : $0["id"]!, "name" : $0["name"]!] }
}

Convert dictionary containing `Any?` to `AnyObject`?

I'm looking for a straightforward way of converting a dictionary of type [String : Any?] to a dictionary of [String: AnyObject]. I could loop through the elements individually, but that just seems 'wrong' to me.
If I just try to cast the original dictionary, I get a crash.
let dict:[String : Any?] = ["something" : "else"]
// crashes with fatal error: 'can't unsafeBitCast
// between types of different sizes'
let newDict = dict as? [String: AnyObject]
Looping is exactly correct, and Swift encourages this. The most efficient and Swift-like (*) approach is:
var result = [String: AnyObject]()
for (key, value) in dict {
if let v = value as? AnyObject {
result[key] = v
}
}
(*) This isn't really "Swift-like" because it includes AnyObject, which should almost never be part of a non-temporary data structure. A proper Swift implementation would convert AnyObject to a real type.
Note that the crash you get is definitely not appropriate and you should open a radar (bugreport.apple.com). An as? should never crash like this. You should either get nil or a compiler failure.
Warning: as #rob-napier mentioned in the comments, its O(n^2) so this approach is only valid for small dictionaries (less than 100 elements).
You can use reduce:
let dict:[String : Any?] = ["something" : "else"]
let newDict = dict.reduce([String: AnyObject](), combine: { accumulator, pair in
var temp = accumulator
temp[pair.0] = pair.1 as? AnyObject
return temp
})
Here newDict is of type [String: AnyObject].