I created a swift class to test Dictionaries. So, I wrote the code below:
import Foundation
class MyClass {
var myFirstDictionary:[String :String]
var myThirdDictionary:[String :String]?
init(){
var mySecondDictionary:[String :String] = [String :String]()
mySecondDictionary["animal"] = "Monkey"
mySecondDictionary.updateValue("something", forKey: "SomeKey")
self.myFirstDictionary = [String :String]()
addOneThingToSecondDictionary()
addAnotherThingToSecondDictionary()
self.myThirdDictionary! = [String :String]()
addOneThingToThirdDictionary()
addAnotherThingToThirdDictionary()
}
func addOneThingToSecondDictionary(){
self.myFirstDictionary["animal"] = "Monkey"
}
func addAnotherThingToSecondDictionary(){
self.myFirstDictionary.updateValue("Superman", forKey: "hero")
}
func addOneThingToThirdDictionary(){
self.myThirdDictionary["animal"]! = "Monkey"
}
func addAnotherThingToThirdDictionary(){
self.myThirdDictionary!.updateValue("Superman", forKey: "hero")
}
}
So, I got 3 errors referring to "myThirdDictionary" :
In the Dictionary initialization compiler said: Could not find an overload for 'init' that accepts the supplied arguments
When I tried to add a Key/value pair in addOneThingToThirdDictionary() : '[String : String]?' does not have a member named 'subscript'
When I tried to add a Key/value pair in addAnotherThingToThirdDictionary() : Immutable value of type '[String : String]' only has mutating members named 'updateValue'
Any thoughts ?
Some of these issues are conceptual errors, and some of them have to do with behaviors that changed in today's Xcode 6 beta 5 release. Running through them all:
This line compiles, but has a superfluous !:
self.myThirdDictionary! = [String :String]()
You don't need to unwrap an optional to assign to it -- it doesn't matter if its current contents are nil if you're providing new contents. Instead, just assign:
self.myThirdDictionary = [String :String]()
Similarly, this line fails because you're subscripting before unwrapping:
self.myThirdDictionary["animal"]! = "Monkey"
This is a problem because you could be subscripting nil if myThirdDictionary has not been initialized. Instead, subscript after checking/unwrapping the optional. As of beta 5, you can use mutating operators or methods through an optional check/unwrap, so the shortest and safest way to do this is:
self.myThirdDictionary?["animal"] = "Monkey"
If myThirdDictionary is nil, this line has no effect. If myThirdDictionary has been initialized, the subscript-set operation succeeds.
This line failed on previous betas because force-unwrapping produced an immutable value:
self.myThirdDictionary!.updateValue("Superman", forKey: "hero")
Now, it works -- sort of -- because you can mutate the result of a force-unwrap. However, force unwrapping will crash if the optional is nil. Instead, it's better to use the optional-chaining operator (which, again, you can now mutate through):
self.myThirdDictionary?.updateValue("Superman", forKey: "hero")
Finally, you have a lot of things in this code that can be slimmed down due to type and scope inference. Here it is with all the issues fixed and superfluous bits removed:
class MyClass {
var myFirstDictionary: [String: String]
var myThirdDictionary: [String: String]?
init(){
var mySecondDictionary: [String: String] = [:]
mySecondDictionary["animal"] = "Monkey"
mySecondDictionary.updateValue("something", forKey: "SomeKey")
myFirstDictionary = [:]
addOneThingToSecondDictionary()
addAnotherThingToSecondDictionary()
// uncomment to see what happens when nil
myThirdDictionary = [:]
addOneThingToThirdDictionary()
addAnotherThingToThirdDictionary()
}
func addOneThingToSecondDictionary(){
myFirstDictionary["animal"] = "Monkey"
}
func addAnotherThingToSecondDictionary(){
myFirstDictionary.updateValue("Superman", forKey: "hero")
}
func addOneThingToThirdDictionary(){
myThirdDictionary?["animal"] = "Monkey"
}
func addAnotherThingToThirdDictionary(){
myThirdDictionary?.updateValue("Superman", forKey: "hero")
}
}
(Changes: Foundation import unused, empty dictionary literal instead of repeated type info)
Simple but effective
let like = (dic.value(forKey: "likes") as? NSDictionary)
print(like?.count)
Or if you want get complete Dictionary count then use:
if let mainDic = dic as? NSDictionary{
print(mainDic.count)
//add your code
}
Related
I have this pretty basic and straight forward mapping of an object into dictionary. I am using and parsing dictionary on the top level. One of its fields is an array of other dictionaries. To set them I use flatMap which seems an appropriate method but since the object inside is not nullable this method suddenly returns tuples (at least it seems that way) instead of dictionaries.
I created a minimum example that can be pasted into a new project pretty much anywhere any of your execution occurs which should give better detail then any description:
func testFlatMap() -> Data? {
class MyObject {
let a: String
let b: Int
init(a: String, b: Int) { self.a = a; self.b = b }
func dictionary() -> [String: Any] {
var dictionary: [String: Any] = [String: Any]()
dictionary["a"] = a
dictionary["b"] = b
return dictionary
}
}
let objects: [MyObject] = [
MyObject(a: "first", b: 1),
MyObject(a: "second", b: 2),
MyObject(a: "third", b: 3)
]
var dictionary: [String: Any] = [String: Any]()
dictionary["objects"] = objects.flatMap { $0.dictionary() }
dictionary["objects2"] = objects.map { $0.dictionary() }
print("Type of first array: " + String(describing: type(of: dictionary["objects"]!)))
print("Type of first element: " + String(describing: type(of: (dictionary["objects"] as! [Any]).first!)))
print("Type of second array: " + String(describing: type(of: dictionary["objects2"]!)))
print("Type of second element: " + String(describing: type(of: (dictionary["objects2"] as! [Any]).first!)))
return try? JSONSerialization.data(withJSONObject: dictionary, options: [])
}
_ = testFlatMap()
So this code crashes saying
'NSInvalidArgumentException', reason: 'Invalid type in JSON write
(_SwiftValue)'
(Using do-catch makes no difference which is the first WTH but let's leave that for now)
So let's look at what the log said:
Type of first array: Array<(key: String, value: Any)>
Type of first element: (key: String, value: Any)
Type of second array: Array<Dictionary<String, Any>>
Type of second element: Dictionary<String, Any>
The second one is what we expect but the first just has tuples in there. Is this natural, intentional?
Before you go all out on "why use flatMap for non-optional values" let me explain that func dictionary() -> [String: Any] used to be func dictionary() -> [String: Any]? because it skipped items that were missing some data.
So only adding that little ? at the end of that method will change the output to:
Type of first array: Array<Dictionary<String, Any>>
Type of first element: Dictionary<String, Any>
Type of second array: Array<Optional<Dictionary<String, Any>>>
Type of second element: Optional<Dictionary<String, Any>>
which means the first solution is the correct one. And on change there are no warnings, no nothing. The app will suddenly just start crashing.
The question at the end is obviously "How to avoid this?" without too much work. Using dictionary["objects"] = objects.flatMap { $0.dictionary() } as [[String: Any]] seems to do the trick to preserve one-liner. But using this seems very silly.
I see that your question boils down to map vs. flatMap. map is the more consistent one and it works as expected here so I won't delve into it.
Now on to flatMap: your problem is one of the reasons that SE-0187 was proposed. flatMap has 3 overloads:
Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element] where S : Sequence
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
When your dictionary() function returns a non-optional, it uses the first overload. Since a dictionary is a sequence of key-value tuples, an array of tuple is what you get.
When your dictionary() function returns an optional, it uses the third overload, which essentially filters out the nils. The situation can be very confusing so SE-0187 was proposed (and accepted) to rename this overload to compactMap.
Starting from Swift 4.1 (Xcode 9.3, currently in beta), you can use compactMap:
// Assuming dictionary() returns [String: Any]?
dictionary["objects"] = objects.compactMap { $0.dictionary() }
For Swift < 4.1, the solution you provided is the only one that works, because it gives the compiler a hint to go with the third overload:
dictionary["objects"] = objects.flatMap { $0.dictionary() } as [[String: Any]]
func resetUserDefaults() {
let userDefaults = UserDefaults.standard
let dict = userDefaults.dictionaryRepresentation()
for (key,_) in dict {
if let key = key as? String {
userDefaults.removeObject(forKey: key)
} else {
#if DEBUG
NSLog("\(key)")
#endif
}
}
}
I'm getting this warning. can anyone suggest me how to avoid this warnoing
All keys in UserDefaults must be of type String. So key is declared as a String. So attempting to cast it to a String is pointless. Hence the warning.
All you need is:
func resetUserDefaults() {
let userDefaults = UserDefaults.standard
let dict = userDefaults.dictionaryRepresentation()
for (key,_) in dict {
userDefaults.removeObject(forKey: key)
}
}
There is no need to cast something to the type that it is already known (to the compiler) to have.
Just remove the whole condition and use your key directly.
Since the keys in the UserDefault should of type String, casting the key to string is of no use, and hence you are getting this warning.
func resetUserDefaults() {
let userDefaults = UserDefaults.standard
let dict = userDefaults.dictionaryRepresentation()
for (key, _) in dict {
userDefaults.removeObject(forKey: key)
}
}
It will always show waring because dictionaryRepresentation() return [String : Any].
So when you cast from string to string it will definitely show warning.
for more see this -> https://developer.apple.com/documentation/foundation/userdefaults/1415919-dictionaryrepresentation
I had the same issue with a private function in Swift 5 and I found a solution working for me.
The solution was to change the value to optional.
I added a question mark after the type I was looking for. (as String"?")
You can see an example here :
private func doSomeThing(completion: #escaping (String) -> ()) {
let Something = somethingElse;
if let anoterThing = something as String?{
completion(anoterThing)
}else{
completion("Error at private func doSomeThing")
}
}
You can find more pieces of information here:
https://docs.swift.org/swift-book/LanguageGuide/OptionalChaining.html
Swift: difference as String? vs. as? String
Downcasting in Swift with as and as?
Best Regards
I'm hoping to write a Swift dictionary extension that will append the reliably same data that eventually gets passed into a web request as URL params. I've tried various tricks, none seem to work.
The closest i've gotten is:
extension Dictionary where Key: StringLiteralConvertible, Value: AnyObject {
mutating func auth() -> Dictionary {
self.updateValue("2.0", forKey: "api")
return self
}
}
But this is still throwing the error:
Cannot invoke 'updateValue' with an argument list of type '(String,
forKey:String)'
on the line that reads, "self.updateValue(..."
Any advice? Thanks in advance!
You just need to cast your string as! Value or as! Key. Also you have declared your method as mutating so you don't need to return anything.
extension Dictionary {
mutating func auth() {
updateValue("2.0" as! Value, forKey: "api" as! Key)
}
}
var dic: [String:AnyObject] = [:]
print(dic) // "[:]\n"
dic.auth()
print(dic) // "["api": 2.0]\n"
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) }
I'm making an extension on Dictionary, just a convenience method to traverse a deep json structure to find a given dictionary that might be present. In the general extension of a Dictionary, i'm not able to subscript because i give a String instead of a Key
extension Dictionary {
func openingHoursDictionary() -> Dictionary<String,AnyObject>? {
if let openingHours = self["openingHours"] as? Array<AnyObject> {
// traverses further and finds opening hours dictionary
}
return nil
}
}
Error: String is not convertible to DictionaryIndex<Key, Value>
on self["openingHours"]
How can i make a Key from the String "openingHours" or check the dictionary for ths string?
You can check at runtime if the string is a valid key for the dictionary:
extension Dictionary {
func openingHoursDictionary() -> [String : AnyObject]? {
if let key = "openingHours" as? Key {
if let openingHours = self[key] as? Array<AnyObject> {
// traverses further and finds opening hours dictionary
}
}
return nil
}
}
But this will "silently" return nil if called for other dictionaries
like [Int, AnyObject].
If you want the compiler to check if it is safe to subscript the
dictionary with a string then you have to use a (generic) function:
func openingHoursDictionary<T>(dict : [String : T]) -> [String : AnyObject]? {
if let openingHours = dict["openingHours"] as? Array<AnyObject> {
// traverses further and finds opening hours dictionary
}
return nil
}
It is (currently) not possible to write Dictionary (or Array)
extension methods that apply only to a restricted type of the
generic parameters.
Just cast the String as Key using "openingHours" as Key
if let pickupPoints = self["openingHours" as Key] as? Array<AnyObject> {
}
Downside is that if doing this i will get a crash if i have a Dictionary<Int,AnyObject> and use the method there.
0x10e8c037d: leaq 0x362aa(%rip), %rax ; "Swift dynamic cast failure"