If I declare an enum
like this:
enum Keys {
case key_one
case key_two
}
I can print it and it will be automatically converted to a String
:
print(Keys.key_one) // prints "key_one"
If I then make a dictionary that maps Strings
to whatever (but let's choose Strings
again for simplicity), I think that it should be able to add a key by using Keys.key_one
as the key, right? Wrong.
var myDict = [String : String]()
/// error: cannot subscript a value of type '[String : String]' with an index
/// of type 'Keys'
myDict[Keys.key_one] = "firstKey"
I can do it if I explicitly convert Keys.key_one
to a String
like this though:
myDict[String(Keys.key_one)] = "firstKey"
print(myDict) // ["key_one": "firstKey"]
So I want to do this without having to wrap my enum
with String()
every time.
I've tried a few things by doing an extension
off of Dictionary
using the where
keyword with the Key
and trying to implement new subscript
functions, but I can't get it to work. What's the trick?
extension Dictionary {
subscript(key: APIKeys) -> Value? {
get {
guard let key = key.stringValue as? Key else { return nil }
return self[key]
}
set(value) {
guard let key = key.stringValue as? Key else { return }
guard let value = value else { self.removeValue(forKey: key); return }
self.updateValue(value, forKey: key)
}
}
}
protocol APIKeys {}
extension APIKeys {
var stringValue: String {
return String(describing: self)
}
}
enum Keys: APIKeys {
case key_one
case key_two
}
var myStringDict = [AnyHashable : Any]()
var model1StringDict = [String : Any]()
var model2StringDict = [String : String]()
myStringDict.updateValue("firstValue", forKey: Keys.key_one.stringValue) // [key_one: firstValue]
myStringDict[Keys.key_two] = "secondValue" // [key_two: secondValue, key_one: firstValue]
myStringDict[Keys.key_one] = nil // [key_two: secondValue]
myStringDict.removeValue(forKey: Keys.key_two.stringValue) // []
model1StringDict.updateValue("firstValue", forKey: Model1Keys.model_1_key_one.stringValue) // [model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue" // [model_1_key_two: secondValue, model_1_key_one: firstValue]
model1StringDict[Model1Keys.model_1_key_one] = nil // [model_1_key_two: secondValue]
model2StringDict.updateValue("firstValue", forKey: Model2Keys.model_2_key_one.stringValue) // [model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue" // [model_2_key_two: secondValue, model_2_key_one: firstValue]
model2StringDict[Model2Keys.model_2_key_one] = nil // [model_2_key_two: secondValue]
I specifically changed the types of the 3 dictionaries to show the common ways of typing a dictionary ([AnyHashable : Any]
, [String : Any]
, and [String : String]
), and showed that this works with each of the types.
It's important to note that if you use updateValue
instead of the assignment operator when the key of your dictionary is AnyHashable
, then you need to specify the String
value of the key with .stringValue
. Otherwise, the exact type of the key being stored will not explicitly be a String
, and it'll get messed up later if you try to, say, remove a value under your key via assigning nil
to that key. For a dictionary where the key is specifically typed to be String
, then the updateValue
function will have a compile time error saying that the key needs to be a String
, so you can't mess it up that way.
I figured out the extension solution that I wanted.
extension Dictionary {
subscript(key: Keys) -> Value? {
get {
return self[String(key) as! Key]
}
set(value) {
guard
let value = value else {
self.removeValueForKey(String(key) as! Key)
return
}
self.updateValue(value, forKey: String(key) as! Key)
}
}
}
enum Keys {
case key_one
case key_two
}
var myStringDict = [String : String]()
/// Adding the first key value through the String() way on purpose
myStringDict.updateValue("firstValue", forKey: String(Keys.key_one))
// myStringDict: ["key_one": "firstValue"]
// myStringDict[Keys.key_one]!: firstValue
myStringDict[Keys.key_two] = "secondValue"
// myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]
myStringDict[Keys.key_one] = nil
// myStringDict: ["key_two": "secondValue"]
Notice that the declared dictionary key type is String
, but I'm able to just use Keys.key_one
and the subscript in the dictionary extension takes care of the rest.
I can probably put some better guarding around the as!
conversion to Key
, but I'm not sure it's needed, as I know that my enum can always be converted to a valid Key
by the String()
cast.
Even better, since I'm using this for API Keys, I made a blank protocol called APIKeys
and each model will implement their own Keys
enum that conforms to the APIKeys
protocol. And the dictionary's subscript is updated to take in APIKeys
as the Key
value.
extension Dictionary {
subscript(key: APIKeys) -> Value? {
get {
return self[String(key) as! Key]
}
set(value) {
guard
let value = value else {
self.removeValueForKey(String(key) as! Key)
return
}
self.updateValue(value, forKey: String(key) as! Key)
}
}
}
protocol APIKeys {}
enum Keys: APIKeys {
case key_one
case key_two
}
enum Model1Keys: APIKeys {
case model_1_key_one
case model_1_key_two
}
enum Model2Keys: APIKeys {
case model_2_key_one
case model_2_key_two
}
var myStringDict = [String : String]()
var model1StringDict = [String : String]()
var model2StringDict = [String : String]()
myStringDict.updateValue("firstValue", forKey: String(Keys.key_one)) // myStringDict: ["key_one": "firstValue"]
myStringDict[Keys.key_two] = "secondValue" // myStringDict: ["key_one": "firstValue", "key_two": "secondValue"]
myStringDict[Keys.key_one] = nil // myStringDict: ["key_two": "secondValue"]
model1StringDict.updateValue("firstValue", forKey: String(Model1Keys.model_1_key_one)) // model1StringDict: ["model_1_key_one": "firstValue"]
model1StringDict[Model1Keys.model_1_key_two] = "secondValue" // model1StringDict: ["model_1_key_one": "firstValue", "model_1_key_two": "secondValue"]
model1StringDict[Model1Keys.model_1_key_one] = nil // model1StringDict: ["model_1_key_two": "secondValue"]
model2StringDict.updateValue("firstValue", forKey: String(Model2Keys.model_2_key_one)) // model2StringDict: ["model_2_key_one": "firstValue"]
model2StringDict[Model2Keys.model_2_key_two] = "secondValue" // model2StringDict: ["model_2_key_one": "firstValue", "model_2_key_two": "secondValue"]
model2StringDict[Model2Keys.model_2_key_one] = nil // model2StringDict: ["model_2_key_two": "secondValue"]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With