kernelpanic
kernelpanic

Reputation: 2956

Conforming Enum to Codable and mapping to model

I'm accessing a public transport API and mapping the response to a model using Codable and Alamofire 5.

It mostly seem to work, but the API isn't normalized correctly, meaning I'm getting different types of data for the same properties (Bus lines can be either Int or String etc...)

I'm trying to map a vehicule type property to an enum that looks like this:

enum VehiculeType {

    case bus
    case trolleybus
    case tram

    init?(rawValue: String) {
        switch rawValue {
        case "AB", "ABA", "ABAA":
            self = .bus
            break
        case "TBA", "TBAA":
            self = .trolleybus
        case "TW6", "TW7", "TW2":
            self = .tram
        default: return nil
        }
    }
}

And here is my model that decodes so far correctly.

struct Departure: Codable {

//    let lineCode: String
    let destination: String
    let waitingTime: Int
    let waitingMilliseconds: Int
    let reroute: String
//    let vehiculeType: VehiculeType?


    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        destination = try container.decode(String.self, forKey: .destination)
        waitingTime = try container.decode(Int.self, forKey: .waitingTime, transformFrom: String.self) ?? 0
        waitingMilliseconds = try container.decode(Int.self, forKey: .waitingMilliseconds)
        reroute = try container.decode(String.self, forKey: .reroute)
//        vehiculeType = try container.decodeIfPresent(String.self, forKey: .vehiculeType, transformFrom: String.self) // This does not work. Correct implementation needed here
    }
}

extension Departure {
    enum CodingKeys: String, CodingKey {
        case destination = "destination"
        case waitingTime = "attente"
        case waitingMilliseconds = "attenteMilli"
        case reroute = "deviation"
//      case vehiculeType
    }
}

Also I have KeyedDecodingContainer extensions implemented for converting some types to another. Float for instance..

How can I automatically map the enum to my model when it is decoded so I get enum values attached to it (see enum), instead of Strings? Can I conform enum directly to Codable?

Upvotes: 0

Views: 296

Answers (1)

gabriellanata
gabriellanata

Reputation: 4336

My recommendation for this would be to go ahead and make VehicleType decodable. Note that you can use Decodable instead of Codable to make things easier and not have to implement the encoding logic if you are never going to be converting that object back anyway.

your final code would look something like this:

enum DecodingError: Error {
case unknownVehiculeType
}

enum VehiculeType: Decodable {

    case bus
    case trolleybus
    case tram

    init(from decoder: Decoder) throws {
        let rawValue = try decoder.singleValueContainer().decode(String.self)
        switch rawValue {
        case "AB", "ABA", "ABAA":
            self = .bus
            break
        case "TBA", "TBAA":
            self = .trolleybus
        case "TW6", "TW7", "TW2":
            self = .tram
        default:
            throw DecodingError.unknownVehiculeType
        }
    }
}

struct Departure: Decodable {

    //    let lineCode: String
    let destination: String
    let waitingTime: Int
    let waitingMilliseconds: Int
    let reroute: String
    let vehiculeType: VehiculeType?


    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        destination = try container.decode(String.self, forKey: .destination)
        waitingTime = try container.decode(Int.self, forKey: .waitingTime, transformFrom: String.self) ?? 0
        waitingMilliseconds = try container.decode(Int.self, forKey: .waitingMilliseconds)
        reroute = try container.decode(String.self, forKey: .reroute)
        vehiculeType = try container.decodeIfPresent(VehiculeType.self, forKey: .vehiculeType)
    }
}

extension Departure {
    enum CodingKeys: String, CodingKey {
        case destination = "destination"
        case waitingTime = "attente"
        case waitingMilliseconds = "attenteMilli"
        case reroute = "deviation"
        case vehiculeType = "vehiculeType"
    }
}

Upvotes: 1

Related Questions