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.

KKG
  • 299
  • 1
  • 4
  • 11

3 Answers3

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!
Alexander
  • 59,041
  • 12
  • 98
  • 151
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).

Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • You don't need the flatmap in this case. You can directly (conditionally) coerce to `[[String: Any]]` – Alexander Aug 15 '16 at 14:59
  • @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 :) – dfrib Aug 15 '16 at 15:04
  • 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 cases – Alexander Aug 15 '16 at 15:17
  • @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. – dfrib Aug 15 '16 at 15:23
  • 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) – Alexander Aug 15 '16 at 15:40
  • @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. – dfrib Aug 15 '16 at 17:10
  • ... 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). – dfrib Aug 15 '16 at 17:10
  • Oh okay, I gotcha now – Alexander Aug 15 '16 at 17:13
  • @AlexanderMomchliov reading my own answer again, I should probably have made the intent of my example more clear! – dfrib Aug 15 '16 at 17:15
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])")
    }
Reginaldo Costa
  • 766
  • 7
  • 17