Generic Subscript on Dictionary

So I've written this code as a bit of a convenience:

extension Dictionary where Key == String, Value == Any {
	subscript<T>(_ key: String) -> T? {
		return self[key] as? T
	}
}

But when I use it

// configuration is Any
guard
	let configurationDict = configuration as? [String: Any]
else { throw ConfigurationError.incorrectlyFormattedConfigurationDictionary }
let foo: String = configurationDict["bar"]

I get the error Cannot convert value of type 'Any?' to specified type 'String'

Am I crazy, or shouldn't that work?

You need to write:

let foo: String? = configurationDict["bar"]

This is because your convenience subscript returns an Optional<T>, but you're assigning it to a value of type String, which is non-optional.

I would NOT redefine the standard subscript operator... better use your own:

extension Dictionary where Key == String, Value == Any {
    subscript<T>(myValueForKey key: String) -> T? {
        get { self[key] as? T }
        set {
            self[key] = newValue
        }
    }
}

Usage:

    let foo: String? = configurationDict[myValueForKey: "bar"]

Also note that it is "String?", not "String".

Alternatively you may define a bunch of:

configurationDict[stringForKey: "bar"]
configurationDict[intForKey: "bar"]
...

Note, the benefit at the use site is minimal, compare with normal:

    let first  = configurationDict[myValueForKey: "bar"] as! String?
    let second = configurationDict[myValueForKey: "bar"] as? String

BTW - there's a difference between first and second here, sometimes you'd want the first (if you are sure that the value must be either absent or of a particular type), and sometimes you'd want the second (when the value could be of a different type and that's not an error situation).

1 Like

Or you could supply the type as an argument to the subscript.

extension [String: Any] {
  public subscript<T>(key: String, as type: T.Type) -> T? {
    self[key] as? type
  }
}

And then you can use it like this.

let foo = configurationDict["bar", as: String.self]
1 Like

I tried this and it also gets the same error.

Edit:

I just realized that, in making an example, this is definitely different than I was doing in my actual code. I was actually running

if let foo: String = configurationDict["bar"] {}

which created the residual, non optional single statement when I was trying to simplify for the forums. However, I still did try both this and my original example and they both provide the same error. I think as @tera is suggesting, I can't replace the standard subscript.

That's totally fair! I thought it be an alternative to the standard, not redefine it. (aka, I thought it would use standard when no type is provided, but use the generic when explicitly defined)

My current solution was basically your alternative: stringForKey etc, but I'll see if the generic will work with my own label.

the “proper” way to define this kind of API would be with a metatype parameter like:

extension [String: Any]
{
    subscript<T>(key:String, as _:T.Type = T.self) -> T? 
    {
        get { self[key] as? T }
        set(value) 
        {
            self[key] = value
        }
    }
}
3 Likes