Deepak Sharma
Deepak Sharma

Reputation: 6477

String to enum mapping in Swift

I have an enum defined in Swift as follows:

 public enum Command:String {

   case first = "FirstCommand"
   case second = "SecondCommand"
   ...
   ...
   case last = "lastCommand"
 }

Now I receive a command dictionary from server and I extract the command string from it. The command string would typically be one of the raw values in Command enum or sometimes it could be a command outside the enum(example, new commands are introduced in future versions of client/server but client is still old). In this scenario, what is the way to use switch statement in Swift 3? How do I typecast the command string to enum and handle the unknown commands in default case of switch?

Upvotes: 12

Views: 15163

Answers (5)

Mazze
Mazze

Reputation: 1404

To further expand the different ways you can solve this is by utilizing the CaseIterable protocol.

public enum Command:String, CaseIterable {
    case first = "FirstCommand"
    case second = "SecondCommand"
    case last = "lastCommand"
    case unknown = "UnknownCommand"
    
    static func getCase(string:String) -> Command {
        return self.allCases.first{"\($0.rawValue)" == string} ?? .unknown
    }
}

The usage would be in this manner:

let textCommand = "SecondCommand"
let unknownCommand = "weirdCommand"

// What is the case label of the "textCommand"
print ("\(Command.getCase(string: textCommand))")

// The case label for our "unknownCommand" is
print ("\(Command.getCase(string: unknownCommand))")

You shift the usage of the null coalescing operator to the Command enum instead. It looks a bit overengineered and it probably is. Enjoy!

Upvotes: 3

Nic Laughter
Nic Laughter

Reputation: 29

It depends on how you're working with it, but you could do something like this:

func getCommand(for string: String) -> Command { 
    switch string {
        case Command.first.rawValue: return .first
        case Command.second.rawValue: return .second
        ...
        default: return .newCommand
     }
 }

I don't think it'd be possible to create new commands from the server in the return, but you could build a class with a command property that takes a String and works differently throughout the app.

Upvotes: 3

Martin R
Martin R

Reputation: 539775

A small variation of Joshua's solution is to add a dedicated case to the enumeration which is used for all unknown commands:

public enum Command:String {

    case first = "FirstCommand"
    case second = "SecondCommand"
    // ...
    case last = "lastCommand"
    case unknown = "<unknownCommand>" 
}

Now you can use the (failable) initializer Command(rawValue:) together with the nil-coalescing operator ?? to map the incoming string to an enumeration value, with all unknown commands being mapped to .unknown:

let command = Command(rawValue: incomingString) ?? .unknown

switch command {
case .first:
    // Handle first command
case .second:
    // Handle second command
case .last:
    // Handle last command
case .unknown:
    // Handle unknown command
}

It does not matter which raw string is used for case unknown, so you could also define

case unknown = "πŸ‘ΊπŸ’©πŸ‘»"

if you like. The compiler verifies that the string is different from all other raw strings. And if the server happens to send that exact string then it would be mapped to .unknown.

Upvotes: 12

Joshua Kaden
Joshua Kaden

Reputation: 1230

I'd attempt to create a Command with a raw value of the incoming string, and only use the switch if it succeeds. If it fails, then handle the unknown commands in another routine.

Like:

guard let command = Command(rawValue: incomingString) else {
    handleUnknownCommand(incomingString)
    return
}

switch command {
    case first:
        ...
}

Upvotes: 22

John Montgomery
John Montgomery

Reputation: 7096

As an alternative structure, you can use optional case values and add a nil handler as the "default" value in the switch:

enum Command: String {
    case first = "a", second = "b", third = "c"
}

let command = Command(rawValue: "d")

switch command {
case .first?:
    print("first")
case .second?:
    print("second")
case .third?:
    print("third")
case nil:
    print("not found")
}
// prints "not found"

Upvotes: 1

Related Questions