Search code examples
jsonswiftenumscodable

Best way to map a JSON string to a custom enum case without having access to the enums implementation in Swift?


Consider the following simple JSON:

{
  "title": "Some title",
  "subtitle": "Some subtitle",
  "button_type": "rounded"
}

This is my current approach towards decoding the buttonType field:

// I dont have access to this enums implementation as it comes from a 3rd party lib.
enum ButtonType {
    case squared
    case simple
    case rounded
    case normal
}

struct ResponseModel: Decodable {
    var title: String
    var subtitle: String
    var type: ButtonType
    
    enum CodingKeys: String, CodingKey {
        case title = "title"
        case subtitle = "subtitle"
        case type = "button_type"
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        title = try container.decode(String.self, forKey: .title)
        subtitle = try container.decode(String.self, forKey: .subtitle)
        let stringType = try container.decode(String.self, forKey: .type)
        switch stringType {
        case "squared":
            type = .squared
        case "simple":
            type = .simple
        case "rounded":
            type = .rounded
        default:
            type = .normal
        }
    }
}

Is there any prettier way to accomplish decoding the string to the custom enum without that nasty switch statement iterating over a plain string?. I sadly do not have access to the enums implementation as it comes from a third party library. If I did I would just make the enum conform to String & Codable and have Codable work its magic, but I dont.

Thanks!


Solution

  • You can create your own enum

    enum MyButtonType: String {
        case squared
        case simple
        case rounded
        case normal
    
        var toButtonType: ButtonType {
             switch self {
                  case .squared: return .squared
                  case .simple: return .simple
                  case .rounded: return .rounded
                  case .normal: return .normal
             }
        }
    }
    

    Then change

     var type: ButtonType
    

    To

     var type: MyButtonType
    

    And when you need the custom enum just use

    responseModel.type.toButtonType
    

    The String after the : in the enum makes it conform to RawRepresentable<String> you won’t need the custom init anymore.

    You can also manually conform to RawPresentable<String> but it requires all the String` entries.

    extension ButtonType: RawRepresentable {
        typealias RawValue = String
        var rawValue: String {
            switch self {
            case .squared:
                return "squared"
            case .simple:
                return "simple"
            case .rounded:
                return "rounded"
            case .normal:
                return "normal"
            }
        }
        
        init?(rawValue: String) {
            switch rawValue {
            case "squared":
                self = .squared
            case "simple":
                self = .simple
            case "rounded":
                self = .rounded
            case "normal":
                self = .normal
            default:
                return nil
            }
        }
    }
    

    With this you don't have to change the Type in the model.