A better way to check dictionary for values - swift

I am currently put in a project that has some beastly code snippet that checks particular values in a dictionary:
guard let userDictionary = Locksmith.loadDataForUserAccount("asdf"), _ = userDictionary["baseUrl"] as? String, _ = userDictionary["refreshToken"] as? String, _ = userDictionary["oauthCode"] as? String, _ = userDictionary["oauthKey"] as? String else { return false }
return true
Are there better ways to do this, such as using something like contains?

If all you want to know is whether the dictionary contains a list of keys, you can test it like this:
Set(d.keys).isSupersetOf(["hey", "ho"])
But if you want to know, for each key, whether the corresponding value is of a certain type that is not the value type of the dictionary (i.e. it requires casting), then the way you are doing it is the only good way.

Related

Filter An Array Of Arrays By A Value In Firestore Swift

I'm looking to filter an array of arrays by specific value of one of the keys located within each array. Each nested array is read in from Firestore.
As an object, each nested array would look like this:
struct Video {
var url: String
var submissionDate: Timestamp
var submittingUser: String
}
I'm reading it in like this:
videos = document.get("videos") as? [[String : Any]] ?? nil
So far so good, but when I filter it like this:
filteredVideos = videos.filter { $0[2].contains(self.userIdentification) }
I can't do it without getting the error "Reference to member 'contains' cannot be resolved without a contextual type," an error which I was unable to find any relevant information on SO about.
I have read that some people say "Don't use arrays in Firestore!" but this is a build requirement.
Anyone have any ideas? Basically just need all arrays within the array where userId == submittingUser.
Reference Article:
I tried the answer from here: How to filter out a Array of Array's but no luck for this situation.
It's actually an array of dictionaries, not an array of arrays. All you need to do is construct the right predicate for the filter.
This is basically what your Firestore data looks like:
let videos: [[String: Any]] = [
["url": "http://www...", "submittingUser": "user1"],
["url": "http://www...", "submittingUser": "user2"]
]
This is the user you're looking for:
let userIdentification = "user2"
This is the predicate for the filer:
let filteredVideos = videos.filter { (video) -> Bool in
if let submittingUser = video["submittingUser"] as? String,
submittingUser == userIdentification {
return true
} else {
return false
}
}
You can shorthand this down to a single line if you're okay with force-unwrapping the dictionary (if you're 100% certain every video will have a valid submittingUser value):
let filteredVideos = videos.filter({ $0["submittingUser"] as! String == userIdentification })

How to pass and get multiple URLQueryItems in Swift?

Ok, I am working in an iMessage app and am trying to parse more than 1 url query item from the selected message here- I have been successful getting/sending just 1 value in a query:
override func willBecomeActive(with conversation: MSConversation) {
// Called when the extension is about to move from the inactive to active state.
// This will happen when the extension is about to present UI.
if(conversation.selectedMessage?.url != nil) //trying to catch error
{
let components = URLComponents(string: (conversation.selectedMessage?.url?.query?.description)!)
//let val = conversation.selectedMessage?.url?.query?.description
if let queryItems = components?.queryItems {
// process the query items here...
let param1 = queryItems.filter({$0.name == "theirScore"}).first
print("***************=> GOT IT ",param1?.value)
}
}
When I just have 1 value, just by printing conversation.selectedMessage?.url?.query?.description I get an optional with that 1 value, which is good. But with multiple I cant find a clean way to get specific values by key.
What is the correct way to parse a URLQueryItem for given keys for iMessage?
When you do conversation.selectedMessage?.url?.query?.description it simply prints out the contents of the query. If you have multiple items then it would appear something like:
item=Item1&part=Part1&story=Story1
You can parse that one manually by splitting the string on "&" and then splitting the contents of the resulting array on "=" to get the individual key value pairs in to a dictionary. Then, you can directly refer to each value by key to get the specific values, something like this:
var dic = [String:String]()
if let txt = url?.query {
let arr = txt.components(separatedBy:"&")
for item in arr {
let arr2 = item.components(separatedBy:"=")
let key = arr2[0]
let val = arr2[1]
dic[key] = val
}
}
print(dic)
The above gives you an easy way to access the values by key. However, that is a bit more verbose. The way you provided in your code, using a filter on the queryItems array, is the more compact solution :) So you already have the easier/compact solution, but if this approach makes better sense to you personally, you can always go this route ...
Also, if the issue is that you have to write the same filtering code multiple times to get a value from the queryItems array, then you can always have a helper method which takes two parameters, the queryItems array and a String parameter (the key) and returns an optional String value (the value matching the key) along the following lines:
func valueFrom(queryItems:[URLQueryItem], key:String) -> String? {
return queryItems.filter({$0.name == key}).first?.value
}
Then your above code would look like:
if let queryItems = components?.queryItems {
// process the query items here...
let param1 = valueFrom(queryItems:queryItems, key:"item")
print("***************=> GOT IT ", param1)
}
You can use iMessageDataKit library. It makes setting and getting data really easy and straightforward like:
let message: MSMessage = MSMessage()
message.md.set(value: 7, forKey: "user_id")
message.md.set(value: "john", forKey: "username")
message.md.set(values: ["joy", "smile"], forKey: "tags")
print(message.md.integer(forKey: "user_id")!)
print(message.md.string(forKey: "username")!)
print(message.md.values(forKey: "tags")!)
(Disclaimer: I'm the author of iMessageDataKit)

<= Operand asking for expected type in Swift?

I'm trying to look for items equal or less than zero in my query like so.....
for zeroItems in snapshot.value as! NSDictionary where zeroItems.value["Value"] as! Int <= 0
I'm getting an Expected type error. Please explain and show me how to correct.
You may want to consider applying a filter to simplify the logic to only include elements less than zero. I've created a working example in the IBM Swift Sandbox.
import Foundation
// Construct the data structure with demo data
struct Snapshot {
let value: NSDictionary
}
let dict: NSDictionary = [ "a" : -1, "b" : 0, "c" : 1]
let snapshot = Snapshot(value: dict)
//Apply a filter
let zeroItems = snapshot.value.filter{Int($0.1 as! NSNumber) <= 0}
//View the result
print(zeroItems)
Almost certainly you've overused Any if you have to use this many as! casts in a single line. Create a custom struct for this type and convert the data to that. If you're passing around NSDictionary and using as! very much, you're fighting the system.
That said, to make this work you probably just need more parentheses. Something like:
for zeroItems in (snapshot.value as! NSDictionary) where (zeroItems.value["Value"] as! Int) <= 0
But this is horrible Swift, so avoid this if possible.
You interpreting snapshot.value as Dictionary, so zeroItems is a tuple of key and value.
As I understand you having array of dictionaries, and you want to filter them by "Value" key, right?
If so then you may use following code:
// dictionary with Value key, and Int value
let dict: [String: Any] = ["Value":-1]
// array of dictionaries
let value: [[String: Any]] = [dict, <**add here as many as you want**>]
// get only array of integeres
let onlyIntegers = value.flatMap { $0["Value"] as? Int }.filter {$0 <= 0 }
print(onlyIntegers)
// get array of dictionaries which passes <=0 check
let onlyLessOrEqualTo0 = value.filter { dict in
if let value = dict["Value"] as? Int {
return value <= 0
}
return false
}
print(onlyLessOrEqualTo0)
First of all, adding some parentheses will help in seeing where the problem is:
for zeroItems in myDict as! NSDictionary where (zeroItems.value["Value"] as! Int) <= 0
Now, we get "Type 'Any' has no subscript members". So the problem is that you haven't told Swift what the type is of zeroItems's value, which I think is a dictionary of , judging from your code:
This compiles for me:
for zeroItems in myDict as! Dictionary<String, Dictionary<String, Int>> where zeroItems.value["Value"]! <= 0
However, this is not pretty. You could probably get more readable code using filter as was suggested in another answer.

What is the proper way to get values from NSDictionaries to separate variables

It's not entirely that I can't solve this, but there are proper and improper ways and I don't want to use a flawed method. I have this snapshot from firebase, and I declare it as NSDictionary as so:
let commentDict = commentsDictionary.value as? NSDictionary
When I print commentDict it looks like this:
commentText = Fififgkffy;
postedBy = USXhV0QYkpbSzAmXgfeteXHuoqg2;
commentText = Fhzifigh;
postedBy = USXhV0QYkpbSzAmXgfeteXHuoqg2;
CommentDict will always look like this. It will always have 2 items. I want to know the proper way to grab the items and assign them to variables. One way I have used before, but just on one item is by putting this in a for loop:
if name as! String == "postedBy" {
Then I get the value for the "postedBy" dictionary item. When having 2 items though, should I make a little array for commentText and postedBY, and have 2 arrays with 2 items each? Another important point is that These must stay in their own groups. I have to keep track of which "commentText" was the first group and which was the 2nd group, same with "postedBy".
In the long-run I have to end up with commentText1, postedBy1, commentText2, postedBy2 all as separate String variables.
This is what I ended up doing but I just want to know if there is a better or more "proper" way to do it. I might have to put some safety if statements into this method to make sure the array gets filled properly and all that.
if let commentDict = commentsDictionary.value as? NSDictionary {
for(name, value) in commentDict {
commentInfoArray.append(value as! String)
}
let comment1Text = commentInfoArray[0]
let comment1User = commentInfoArray[1]
let comment2Text = commentInfoArray[2]
let comment2User = commentInfoArray[3]
}
If it's only postedBy and commentText relation that you are looking for try using this as your structure:-
USXhV0QYkpbSzAmXgfeteXHuoqg2 : comment1 : true,
comment2 : true,
//postedById : [postText]
Then at your retrieval use a dictionary to append your data to:-
db_Ref.child(userID).observeSingleEvent(of: .value, with: {(snap) in
if let commentDict = snap.value as? [String:AnyObject]{
for each in commentDict{
print(each.key) // Append your key here
}
}
})

Swift guard else called on dictionary key with NULL value

If I have a Dictionary returned from a NSNotification containing the following
print(notificationObj.object)
Optional({
age = "<null>";
names = (
David
);
})
Then the guard else is called when trying to assign this to a variable:
guard let categories = notificationObj.object as? [String:[String]] else {
// Gets to here
return
}
How can I handle the case where a Dictionary key is null.
Your dictionary does contain ...
Optional({
age = "<null>";
names = (
David
);
})
... and ...
age = ... is String = String (value is single String),
names = ( ... ) is String = [String] (value is array of Strings).
You can't cast it to [String:[String]] because the first pair doesn't fit this type. This is the reason why your guard statement hits else.
Hard to answer your question. Dictionary contains names, you want categories, names key does contain David, which doesn't look like category, ... At least you know why guard hits else.
Your questions is not very clear.
However IF
You have a dictionary declared as follow [String:[String]]
And you want manage the scenario where a given key is not present
Like this
let devices : [String:[String]] = [
"Computers": ["iMac", "MacBook"],
"Phones": ["iPhone 6S", "iPhone 6S Plus"]
]
Then you can at least 2 solutions
1. conditional unwrapping
if let cars = devices["Car"] {
// you have an array of String containing cars here
} else {
print("Ops... no car found")
}
2. guard let
func foo() {
guard let cars = devices["Car"] else {
print("Ops... no car found")
return
}
// you have an array of String containing cars here...
cars.forEach { print($0) }
}
It appears that your printed notificationObject.object is constructed from a JSON string that looks like this:
"{ \"age\": null, \"names\":[\"David\"] }"
The reason that you are hitting your else clause is because age is actually a nil, and not a valid String array. I tried using [String: [String]?] and [String: NSArray?] neither of which seem to work. The type is actually an NSNull (which inherits from NSObject).
So you can cast to [String: AnyObject] and check for NSArray like this:
if let categories = j as? [String: AnyObject] where (categories["age"] is NSArray) {
print("age was array")
} else {
print("age is probably null")
}
You might be better off if your notification object simply omitted the "age" property when the value is null. Then you would be able to cast to [String: [String]].