9

I have this enum:

enum DealStatus:String {
    case PENDING = "Pending"
    case ACTIVE = "Active"
    case STOP = "Stop"
    case DECLINED = "Declined"
    case PAUSED = "Paused"
}

and struct:

struct ActiveDeals: Decodable {
    let keyword:            String
    let bookingType:        String
    let expiryDate:         Int
    let createdAt:          Int?
    let shopLocation:       String?
    let dealImages:         [DealImages]?
    let dealStatus:         String?
    let startingDate:       Int?
}

In struct I am trying to assign enum as type for dealStatus like this:

struct ActiveDeals: Decodable {
        let keyword:            String
        let bookingType:        String
        let expiryDate:         Int
        let createdAt:          Int?
        let shopLocation:       String?
        let dealImages:         [DealImages]?
        let dealStatus:         DealStatus
        let startingDate:       Int?
    }

But I am getting some compiler error:

Type 'ActiveDeals' does not conform to protocol 'Decodable'

Protocol requires initializer 'init(from:)' with type 'Decodable' (Swift.Decodable)

Cannot automatically synthesize 'Decodable' because 'DealStatus' does not conform to 'Decodable'

Community
  • 1
  • 1
Sushil Sharma
  • 2,321
  • 3
  • 29
  • 49

3 Answers3

17

The problem is that Swift can automatically synthesize the methods needed for Decodable only if all the properties of a struct are also Decodable and your enum is not Decodable.

Having just had a go in a playground, it seems you can make your enum Decodable just by declaring that it is and Swift will automatically synthesize the methods for you. i.e.

enum DealStatus:String, Decodable  
//                      ^^^^^^^^^ This is all you need
{
    case PENDING = "Pending"
    case ACTIVE = "Active"
    case STOP = "Stop"
    case DECLINED = "Declined"
    case PAUSED = "Paused"
}
JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • Thanks for the answer. Now I am getting this error while parsing: dataCorrupted(Swift.DecodingError.Context(codingPath: [O3.ActiveDealsContent.(CodingKeys in _300B97B81632ECD4F45F86E6B039153F).content, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)), O3.ActiveDeals.(CodingKeys in _300B97B81632ECD4F45F86E6B039153F).dealStatus], debugDescription: "Cannot initialize DealStatus from invalid String value PENDING", underlyingError: nil)) – Sushil Sharma Feb 14 '18 at 10:01
  • 1
    @SushilSharma Yes. Your data contains the string `PENDING`. The raw value for your `PENDING` case is `Pending`. Make your raw value upper case. – JeremyP Feb 14 '18 at 10:03
  • But I needed raw value CamelCased. Because I need to display raw value in one of my Labels. – Sushil Sharma Feb 14 '18 at 10:19
  • 2
    @SushilSharma Then either make your data camel cased or create a custom `description` for the `enum` that returns the value in "display format". Or implement `init(from:)` to do the conversion. – JeremyP Feb 14 '18 at 10:24
4

The error says mean that some of the properties of class do not conform to Decodable protocol.

Add Decodable conformance to your enum and it should be fine.

extension DealStatus: Decodable { }
Sandeep
  • 20,908
  • 7
  • 66
  • 106
  • `Codable => Decodable` – JeremyP Feb 14 '18 at 09:43
  • Yes, Decodable. Thanks – Sandeep Feb 14 '18 at 09:45
  • @Sandeep Thanks for the answer. Now I am getting this error while parsing: dataCorrupted(Swift.DecodingError.Context(codingPath: [O3.ActiveDealsContent.(CodingKeys in _300B97B81632ECD4F45F86E6B039153F).content, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0", intValue: Optional(0)), O3.ActiveDeals.(CodingKeys in _300B97B81632ECD4F45F86E6B039153F).dealStatus], debugDescription: "Cannot initialize DealStatus from invalid String value PENDING", underlyingError: nil)) – Sushil Sharma Feb 14 '18 at 10:00
2

According to Federico Zanetello on his post Swift 4 Decodable: Beyond The Basics, Codable and Decobable protocols will works fine if you need to parse a subset of primitives (strings, numbers, bools, etc).

On your case, just making DealStatus conform to Decodable (as suggested by JeremyP) should solve your problem. You can check on Playgrounds creating your own JSON data and trying to parse it:

import UIKit

enum DealStatus: String, Decodable {
    case PENDING = "Pending"
    case ACTIVE = "Active"
    case STOP = "Stop"
    case DECLINED = "Declined"
    case PAUSED = "Paused"
}

struct ActiveDeals: Decodable {
    let keyword:            String
    let bookingType:        String
    let expiryDate:         Int
    let createdAt:          Int?
    let shopLocation:       String?
    let dealStatus:         DealStatus
    let startingDate:       Int?
}

let json = """
{
    "keyword": "Some keyword",
    "bookingType": "A type",
    "expiryDate": 123456,
    "createdAt": null,
    "shopLocation": null,
    "dealStatus": "Declined",
    "startingDate": 789456
}
""".data(using: .utf8)!

do {
    let deal = try JSONDecoder().decode(ActiveDeals.self, from: json)
    print(deal)
    print(deal.dealStatus)
} catch {
    print("error info: \(error)")
}

And the output will be:

ActiveDeals(keyword: "Some keyword", bookingType: "A type", expiryDate: 123456, createdAt: nil, shopLocation: nil, dealStatus: __lldb_expr_61.DealStatus.DECLINED, startingDate: Optional(789456))
DECLINED

However, to become a better programmer you should always be curious and try to learn how things, so if you are interested on how you could conform to Decodable protocol (let's say you need custom keys, custom errors or some more complex data structure), this is how you can do it:

import UIKit

enum DealStatus: String {
    case PENDING = "Pending"
    case ACTIVE = "Active"
    case STOP = "Stop"
    case DECLINED = "Declined"
    case PAUSED = "Paused"
}

struct ActiveDeals {
    let keyword:            String
    let bookingType:        String
    let expiryDate:         Int
    let createdAt:          Int?
    let shopLocation:       String?
    let dealStatus:         DealStatus
    let startingDate:       Int?
}

extension ActiveDeals: Decodable {
    enum StructKeys: String, CodingKey {
        case keyword = "keyword"
        case bookingType = "booking_type"
        case expiryDate = "expiry_date"
        case createdAt = "created_at"
        case shopLocation = "shop_location"
        case dealStatus = "deal_status"
        case startingDate = "starting_date"
    }

    enum DecodingError: Error {
        case dealStatus
    }

    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: StructKeys.self)

        let keyword = try container.decode(String.self, forKey: .keyword)
        let bookingType = try container.decode(String.self, forKey: .bookingType)
        let expiryDate = try container.decode(Int.self, forKey: .expiryDate)
        let createdAt = try container.decode(Int?.self, forKey: .createdAt)
        let shopLocation = try container.decode(String?.self, forKey: .shopLocation)

        //Get deal status as a raw string and then convert to your custom enum
        let dealStatusRaw = try container.decode(String.self, forKey: .dealStatus)
        guard let dealStatus = DealStatus(rawValue: dealStatusRaw) else {
            throw DecodingError.dealStatus
        }

        let startingDate = try container.decode(Int?.self, forKey: .startingDate)

        self.init(keyword: keyword, bookingType: bookingType, expiryDate: expiryDate, createdAt: createdAt, shopLocation: shopLocation, dealStatus: dealStatus, startingDate: startingDate)
    }
}

let json = """
{
    "keyword": "Some keyword",
    "booking_type": "A type",
    "expiry_date": 123456,
    "created_at": null,
    "shop_location": null,
    "deal_status": "Declined",
    "starting_date": 789456
}
""".data(using: .utf8)!

do {
    let deal = try JSONDecoder().decode(ActiveDeals.self, from: json)
    print(deal)
    print(deal.dealStatus)
} catch {
    print("error info: \(error)")
}

In this case the output is still the same:

ActiveDeals(keyword: "Some keyword", bookingType: "A type", expiryDate: 123456, createdAt: nil, shopLocation: nil, dealStatus: __lldb_expr_67.DealStatus.DECLINED, startingDate: Optional(789456))
DECLINED