type any? has no subscript members
Asked Answered
D

3

19

I want to get Addresses from profile dictionary,but I got the error "type any? has no subscript members"

var address:[[String : Any]] = [["Address": "someLocation", "City": "ABC","Zip" : 123],["Address": "someLocation", "City": "DEF","Zip" : 456]]
var profile:[String : Any] = ["Name": "Mir", "Age": 10, "Addresses": address]
profile["Addresses"][0]     <-----------------type any? has no subscript members

How can I fix it and get the address? Thanks a lot.

Disincentive answered 15/8, 2016 at 14:13 Comment(3)
Try to rewrite your code without use type AnyRael
You better use classes or structs for address and profile, I believe.Rael
Please don't use Dictionaries like this.Gazelle
G
29

When you subscript profile with "Addresses", you're getting an Any instance back. Your choice to use Any to fit various types within the same array has caused type erasure to occur. You'll need to cast the result back to its real type, [[String: Any]] so that it knows that the Any instance represents an Array. Then you'll be able to subscript it:

func f() {
    let address: [[String : Any]] = [["Address": "someLocation", "City": "ABC","Zip" : 123],["Address": "someLocation", "City": "DEF","Zip" : 456]]
    let profile: [String : Any] = ["Name": "Mir", "Age": 10, "Addresses": address]

    guard let addresses = profile["Addresses"] as? [[String: Any]] else {
        // Either profile["Addresses"] is nil, or it's not a [[String: Any]]
        // Handle error here
        return
    }

    print(addresses[0])
}

This is very clunky though, and it's not a very appropriate case to be using Dictionaries in the first place.

In such a situation, where you have dictionaries with a fixed set of keys, structs are a more more appropriate choice. They're strongly typed, so you don't have to do casting up and down from Any, they have better performance, and they're much easier to work with. Try this:

struct Address {
    let address: String
    let city: String
    let zip: Int
}

struct Profile {
    let name: String
    let age: Int
    let addresses: [Address]
}

let addresses = [
    Address(
        address: "someLocation"
        city: "ABC"
        zip: 123
    ),
    Address(
        address: "someLocation"
        city: "DEF"
        zip: 456
    ),
]

let profile = Profile(name: "Mir", age: 10, addresses: addresses)

print(profile.addresses[0]) //much cleaner/easier!
Gazelle answered 15/8, 2016 at 14:43 Comment(4)
Thank you for your teach. Your explanation is wonderful. It's another way to fix various types within the same array?Disincentive
Huh, Is that a question?Gazelle
Yes,Is it has another way to fix various types within the same array? (Sorry my english is poor.. :(Disincentive
Not really, because it's not really something that is meant to be done. Stricter are the way to do thisGazelle
C
4

You should re-think how you've chosen to construct adress and profile; see e.g. Alexander Momchliov's answer.


For the technical discussion, you could extract the Any members of profile that you know to contain [String: Any] dictionaries wrapped in an Any array; by sequential attempted type conversion of profile["Addresses"] to [Any] followed by element by element (attempted) conversion to [String: Any]:

if let adressDictsWrapped = profile["Addresses"] as? [Any] {
    let adressDicts = adressDictsWrapped.flatMap{ $0 as? [String: Any] }
    print(adressDicts[0]) // ["Zip": 123, "City": "ABC", "Address": "someLocation"]
    print(adressDicts[1]) // ["Zip": 456, "City": "DEF", "Address": "someLocation"]
}

or, without an intermediate step ...

if let adressDicts = profile["Addresses"] as? [[String: Any]] {
   print(adressDicts[0]) // ["Zip": 123, "City": "ABC", "Address": "someLocation"]
   print(adressDicts[1]) // ["Zip": 456, "City": "DEF", "Address": "someLocation"]
}

But this is just a small lesson in attempted typed conversion (-> don't do this).

Chicoine answered 15/8, 2016 at 14:51 Comment(9)
You don't need the flatmap in this case. You can directly (conditionally) coerce to [[String: Any]]Gazelle
@AlexanderMomchliov yes, that version is included in the 2nd code block in my answer (but I included that just 2 minutes before your comment, so maybe it wasn't there when you started your comment): the first alternative takes the casting step by step in the same nested Any order that the original profile and address variables were defined in (extra explicitly explanatory :)Chicoine
Ah yeah, that's probably what happened. The iterative casting isnt actually the intermediate step that occurs. Down casting is always constant time. Iirc, Only upcasting requires that looping, and only in some casesGazelle
@AlexanderMomchliov ah you misunderstood me (probably unclear wording on my behalf), I referred to the "nestedness of Any's" in the definitions of the original profile and address properties: the first block is stricly explicitly explanatory w.r.t. re-casting to the types wrapped in the nested Any's.Chicoine
Yes, I got that. I'm saying that in the first example, where you use the flat map as the intermediate step that occurs under the hood. Down casting is always constant time. Only upcasting sometimes requires iteration (IIRC)Gazelle
@AlexanderMomchliov We're talking around each other :) I'm not describing the intermediate steps of casting, I'm (attempting) to show explicitly for the OP that profile["Addresses"] is an Any instance that don't even know that it's a collection; an inherent property of Any and one of the reasons to avoid it: it just wraps anything. As first step we tell adressDictsWrapped that its a collection of something (anything) unknown. Next steps, the Any members of this coll. naturally don't know themselves that they are dictionaries, so we tell with the flatMap conversion that they are.Chicoine
... Again, not attempting to show what happens behind the hood for a direct [[String: Any]] conversion, but attempting to shine light on the weak type information (... none) of Any instances: it can't even realize its a collection (array) of wrapped instances, and it cant realize its a collection of collections (dicts). I thought this would somewhat point out how a bad solution it is to use Any, particularly as we can't even know how deep the "core element" (non-collection element) of any given Any instance is (unless resorting to runtime introspection hacks).Chicoine
Oh okay, I gotcha nowGazelle
@AlexanderMomchliov reading my own answer again, I should probably have made the intent of my example more clear!Chicoine
M
0

I agree that if you rethink your design as suggested earlier. For discussion sake you can perform the following to achieve what you are seeking.

var address:[[String : Any]] = [["Address": "someLocation", "City": "ABC","Zip" : 123],["Address": "someLocation", "City": "DEF","Zip" : 456]]
var profile:[String : Any] = ["Name": "Mir", "Age": 10, "Addresses": address]
if let allAddresses = profile["Addresses"] as? [[String:Any]] {
    print("This are all the address \(allAddresses[0])")
    }
Maleki answered 15/8, 2016 at 15:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.