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

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"]!] }
}

Related

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")

<= 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.

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].

Swift - matching class dynamically

I am trying to write an extension method on Dictionary with the following signature:
func firstNonNilObjectForKey(keys: [Key], ofClass aClass: Any.Type = String.self) -> Value?
This is meant to help me deal with JSON dictionaries, where I often need the first non-null value, but sometimes the JSON includes null itself as value, which is converted to NSNull in Cocoa.
The usage would be something like:
let dict: [String:AnyObject] = [...]
let myValue = dict.firstNonNilObjectForKey([ "key1", "key2" ]) as? String
The issue is implementational - how to match the class:
if let value = self[key] {
if value is aClass { ... } <--- error: aClass is not a type
if let castedValue = value as? aClass { ... } <--- error: ditto
let type = value.dynamicType
if type == aClass { ... } <--- no error, but doesn't handle subclasses!
// Cannot use value.isKindOfClass() since value may be Any
}
I have thought of an alternative:
func firstNonNilObjectForKey<ReturnValueType>(keys: [Key]) -> ReturnValueType?
which allows to be implemented as
if let value = self[key] as? ReturnValueType { ... }
But:
- Here I cannot set the default type (I mostly need String.Type).
- I'm not really sure how to invoke this:
let value = dict.firstNonNilObjectForKey([ "key1", "key2" ]) as? String <--- error: Cannot invoke 'firstNonNilObjectForKey' with an argument list of type '([String])'
let value = dict.firstNonNilObjectForKey<String>([ ... ]) <--- error: Cannot explicitly specialize a generic function
I hope this isn't a duplicate, but most similar questions here are simply handling a situation where you are matching against a known class.
I'm not sure I got the requirements exactly, but can something like this work for you?
extension Dictionary {
func firstNonNilObjectForKey(keys: [Key]) -> String? {
return self.firstNonNilObjectForKey(keys, ofClass: String.self)
}
func firstNonNilObjectForKey<T>(keys: [Key], ofClass aClass: T.Type) -> T? {
for k in keys {
if let v = self[k] as? T {
return v
}
}
return nil
}
}
let dict = ["key1": 2, "key2": "Foo"]
let aString = dict.firstNonNilObjectForKey(["key1", "key2"]) // "Foo"
let anInt = dict.firstNonNilObjectForKey(["key1", "key2"], ofClass: Int.self) // 2
The main gotcha here is that I'm using overloading as opposed to default parameters, which don't seem to get along well with the swift type checker.

Updating a nested value in an NSDictionary

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.