Jordan H
Jordan H

Reputation: 55665

Access String value in enum without using rawValue

I would like to replace my global string constants with a nested enum for the keys I'm using to access columns in a database.

The structure is as follows:

enum DatabaseKeys {

    enum User: String {
        case Table = "User"
        case Username = "username"
        ...
    }

    ...

}

Each table in the database is an inner enum, with the name of the table being the enum's title. The first case in each enum will be the name of the table, and the following cases are the columns in its table.

To use this, it's pretty simple:

myUser[DatabaseKeys.User.Username.rawValue] = "Johnny"

But I will be using these enums a lot. Having to append .rawValue to every instance will be a pain, and it's not as readable as I'd like it to be. How can I access the String value without having to use rawValue? It'd be great if I can do this:

myUser[DatabaseKeys.User.Username] = "Johnny"

Note that I'm using Swift 2. If there's an even better way to accomplish this I'd love to hear it!

Upvotes: 36

Views: 14246

Answers (5)

SleepNot
SleepNot

Reputation: 3038

You can use callAsFunction (New in Swift 5.2) on your enum that conforms to String.

enum KeychainKey: String {
   case userId
   case email
}

func callAsFunction() -> String {
    return self.rawValue
}

usage:

KeychainKey.userId()

Upvotes: 15

feca
feca

Reputation: 1169

You can do this with custom class:

enum Names: String {
    case something, thing
}

class CustomData {
    subscript(key: Names) -> Any? {
        get {
            return self.customData[key.rawValue]
        }

        set(newValue) {
            self.customData[key.rawValue] = newValue
        }
    }

    private var customData = [String: Any]()
}

...

let cData = CustomData()
cData[Names.thing] = 56

Edit:
I found an another solution, that working with Swift 3:

enum CustomKey: String {
    case one, two, three
}

extension Dictionary where Key: ExpressibleByStringLiteral {
    subscript(key: CustomKey) -> Value? {
        get {
            return self[key.rawValue as! Key]
        }
        set {
            self[key.rawValue as! Key] = newValue
        }
    }
}

var dict: [String: Any] = [:]
dict[CustomKey.one] = 1
dict["two"] = true
dict[.three] = 3
print(dict["one"]!)
print(dict[CustomKey.two]!)
print(dict[.three]!)

Upvotes: 5

Qbyte
Qbyte

Reputation: 13243

If you are able to use User as dictionary key instead of String (User is Hashable by default) it would be a solution.

If not you should use yours with a nested struct and static variables/constants.

Upvotes: 0

Jordan H
Jordan H

Reputation: 55665

While I didn't find a way to do this using the desired syntax with enums, this is possible using structs.

struct DatabaseKeys {

    struct User {
        static let identifier = "User"
        static let Username = "username"
    }

}

To use:

myUser[DatabaseKeys.User.Username] = "Johnny"

Apple uses structs like this for storyboard and row type identifiers in the WatchKit templates.

Upvotes: 38

Sandeep
Sandeep

Reputation: 21134

You can use CustomStringConvertible protocol for this.

From documentation,

String(instance) will work for an instance of any type, returning its description if the instance happens to be CustomStringConvertible. Using CustomStringConvertible as a generic constraint, or accessing a conforming type's description directly, is therefore discouraged.

So, if you conform to this protocol and return your rawValue through the description method, you will be able to use String(Table.User) to get the value.

enum User: String, CustomStringConvertible {

    case Table = "User"
    case Username = "username"

    var description: String {
        return self.rawValue
    }
}

var myUser = [String: String]()
myUser[String(DatabaseKeys.User.Username)] = "Johnny"

print(myUser) // ["username": "Johnny"]

Upvotes: 19

Related Questions