Home > Net >  Best way to map a JSON string to a custom enum case without having access to the enums implementatio
Best way to map a JSON string to a custom enum case without having access to the enums implementatio

Time:11-05

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!

CodePudding user response:

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.

CodePudding user response:

Just another solution.

Even if you don't control the how ButtonType is defined, you can still conform it to Decodable using an extension.

Step 1: Conform ButtonType to Decodable

extension ButtonType: Decodable {
  init(from decoder: Decoder) throws {
    let container = try decoder.singleValueContainer()
    let text = try container.decode(String.self)
    switch text {
    case "squared": self = .squared
    case "simple": self = .simple
    case "rounded": self = .rounded
    case "normal": self = .normal
    default: throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Cannot initialize ButtonType from invalid String value \(text)"))
    }
  }
}

Step 2: Use it in your ResponseModel

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"
    }
}
  • Related