How to access nested dictionary? - swift

I can print this in the debugger:
(lldb) print params["message"]!
([String : String]) $R5 = 2 key/value pairs {
[0] = (key = "body", value = "iPadUser has started a new stream")
[1] = (key = "title", value = "Stream started")
}
But I am trying to figure out how to access the body and title separately.
I construct params in this way:
let recipients = ["custom_ids":[recips]]
let notificationDetails = "hello there"
let content = [
"title":title,
"body":details
]
let params: [String:Any] = [
"group_id":"stream_requested",
"recipients": recipients,
"message": content
]

print((params["message"] as! [String: Any])["title"] as! String)
You need to cast the Dictionary value as specific type, since the compiler doesn't know what to expect. (Please mind that you mustn't use force unwrap in other way than example code.)
Considering you need to fetch array values when recipients dictionary looks like this:
let recipients = ["custom_ids":["recipe1", "recipe2", "etc"]]
get to the ids like this:
guard let recipients = params["recipients"] as? [String: Any],
let customIDs = recipients["custom_ids"] as? [String]
else { return }
for id in customIDs {
print(id) // Gets your String value
}

Related

Swift Firebase - Convert database snapshot into an array

I have a groups reference in firebase that looks like this:
I'm having trouble converting the list of members into an array of strings in my app.
I'm fetching the data like so:
//Reference to each group
let ref = Database.database().reference().child("groups").child(snapshot.key)
//Get the group data from the reference
ref.observeSingleEvent(of: .value, with: { (groupSnap) in
//Cast data as dictionary [String:Any]
if let dictionary = groupSnap.value as? [String: Any] {
//Parse each group object
if let group = Group.parse(snapshot.key, dictionary) {
groups.insert(group, at: 0)
}
//Escape with group array
complete(groups)
}
})
And currently parsing the data without the members:
static func parse(_ key: String, _ data: [String:Any]) -> Group? {
let name = data["name"] as! String
let category = data["category"] as! String
let owner = data["owner"] as! String
return Group(id: key, name: name, category: Group.Category(rawValue: category)!, ownerId: owner, members: nil)
}
How would I turn the members list into an array of strings for my group object?
// example data
let data = [
// "name": ...
// "category": ...
// "owner": ...
"members": [
"member1": true,
"member2": false,
"member3": true,
"member4": true
]
]
// grabbing the members element like you do in your parse function
let members = data["members"] as! [String: Bool]
let membersAsListOfStrings = Array(members.keys)
print(membersAsListOfStrings) // -> ["member4", "member1", "member3", "member2"]
let filteredMembersAsListOfStrings = Array(members.filter { $0.value }.keys)
print(filteredMembersAsListOfStrings) // -> ["member4", "member3", "member1"]
You're looking for the .keys attribute. I believe all dictionaries in Swift have this. This code ran for me fine in a playground.

Jumping into swift - trouble with array of dictionary from json

json comes in and is an array of dictionary:
let dict = try JSONSerialization.dictionary(data: data, options: .allowFragments)
(key: contact_383348580, value: {
email = "r#c.com";
"first_name" = Jon;
"last_name" = B;
tags = "";
})
(key: contact_445575065, value: {
email = "n.w#s.com";
"first_name" = "<null>";
"last_name" = "<null>";
tags = "";
})
Trying to map this to a User class (and then sort alpha by first name(?)) and then populate a tableview.
I'm all obj-c but trying to add this feature in my objc project through swift to improve my skills incrementally. But googling returns all different ways with different swift versions that don't seem to work (or frankly make sense to me anyway but I'll get there).
Currently I can create users but the names are empty.
for objects in dict {
print(objects)
let first = dict["first_name"] as? String
let last = dict["last_name"] as? String
let name = "\(first ?? "asas") \(last ?? "sdasd")"
let object = User(username: name)
contacts.append(object!)
}
print(" contacts \(contacts)")
You say you have an array of dictionaries and if that is correct then you have fooled yourself with the naming of your variables because dict is an array and objects a dictionary. I assume your code doesn't compile?
What about this
let array = try JSONSerialization.dictionary(data: data, options: .allowFragments)
for dict in array {
let first = dict["first_name"] as? String ?? "asas"
let last = dict["last_name"] as? String ?? "sdasd"
let name = "\(first) \(last)"
let object = User(username: name)
contacts.append(object)
}

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.

Type 'Any' has no subscript members when accessing dictionary in UserDefaults

I am trying to access the values of a dictionary which I passed into UserDefaults, however I get this error:
Type 'Any' has no subscript members.
I have looked around on StackOverflow for answers but everything I tried is failing.
This is the Dictionary object that I stored in UserDefaults and I am trying to access Authorities.levelAcess:
{
accountEnabled = 0;
accountLocked = 0;
Authorities = {
levelAccess = "tier2";
accessType = "3rd party";
};
message = "Logged in Successfully";
profile = "trust";
roles = (
{
authority = "admin";
}
);
status = 1;
}
This the code for UserDefaults:
let keyDict: Dictionary<String, AnyObject> = json
UserDefaults.standard.set(json, forKey: "dict")
This is how I am trying to access the dictionary:
The reason of showing this error in your code is: the compiler cannot recognize result as dictionary, if you tried to option and click on result you would see that its type is Any. You have to cast it first as [String: AnyObject] and then get "Authorities" form it.
It should be like this:
if let result = UserDefaults.standard.dictionary(forKey: "dict") {
print(result)
}
That's how you should "optional binding" the dictionary, thus (assumeing that "Authorities" is a [String: String]):
if let addresses = result["Authorities"] as? [String: String] {
print(addresses)
}
You could also do it as one step:
if let result = UserDefaults.standard.dictionary(forKey: "dict"),
let addresses = result["Authorities"] as? [String: String] {
print(result)
print(addresses)
let levelAccess = addresses["levelAccess"]
print(levelAccess) // optional
}
Finally, you could get the levelAcess from addresses as:
let levelAccess = addresses["levelAccess"]
Again, note that levelAccess would be an optional string (String?), which means you should also handle it.
For fetching a dictionary from UserDefaults, use
dictionary(forKey: key)
In your case:
var result = UserDefaults.standard.dictionary(forKey: "dict")

Dictionary in swift

let json: [AnyObject] = {
"response": "get_nearby_deals",
"userID": "12345",
"demo":[{"deal_code":"iD1612061"}]
}
How to declare Dictionary in Swift? I'm new in Swift. Totally stuck.
You have declared Array using [AnyObject], just change it to [String: Any] and replace curly braces {} with square brackets [].
let json: [String: Any] = [
"response": "get_nearby_deals",
"userID": "12345",
"demo":[["deal_code":"iD1612061"]]
]
And you can retrieve value from Dictionary using subscript like this.
let userID = json["userID"] as! String
//Above will crash if `userID` key is not exist or value is not string, so you can use optional wrapping with it too.
if let userID = json["userID"] as? String {
print(userID)
}
//`demo` value is an array of [String:String] dictionary(s)
let demo = json["demo"] as! [[String:String]]
//Same here as the previous `userID`, it will crash if 'demo' key is not exist, so batter if you optionally wrapped it:
if let demo = json["demo"] as? [[String:String]] {
print(demo)
}