Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

String to enum mapping in Swift

Tags:

enums

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?

like image 704
Deepak Sharma Avatar asked May 19 '17 16:05

Deepak Sharma


4 Answers

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.

like image 63
Martin R Avatar answered Oct 05 '22 18:10

Martin R


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!

like image 20
Mazze Avatar answered Oct 05 '22 19:10

Mazze


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:
        ...
}
like image 21
Joshua Kaden Avatar answered Oct 05 '22 19:10

Joshua Kaden


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.

like image 31
Nic Laughter Avatar answered Oct 05 '22 19:10

Nic Laughter