枚举类型属性的可解码一致性

9

我有这个枚举:

enum DealStatus:String {
    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 dealImages:         [DealImages]?
    let dealStatus:         String?
    let startingDate:       Int?
}

在结构体中,我尝试将枚举类型作为dealStatus的类型进行赋值,如下所示:
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?
    }

但是我遇到了一些编译器错误:

类型“ActiveDeals”不符合协议“Decodable”

该协议要求使用类型为“Decodable”(Swift.Decodable)的初始化器“init(from:)”

无法自动生成“Decodable”,因为“DealStatus”未符合“Decodable”

3个回答

17

问题在于,如果一个结构体的所有属性都是 Decodable 类型,但是枚举类型不是 Decodable 类型,那么 Swift 只能自动合成用于 Decodable 的方法。

在 playground 中试验后发现,你可以通过声明枚举类型为 Decodable 类型来使其成为可解码类型,Swift 会自动帮你合成相应的方法。例如:

enum DealStatus:String, Decodable  
//                      ^^^^^^^^^ This is all you need
{
    case PENDING = "Pending"
    case ACTIVE = "Active"
    case STOP = "Stop"
    case DECLINED = "Declined"
    case PAUSED = "Paused"
}

感谢您的回答。现在我在解析时遇到了这个错误: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:“无法从无效的字符串值PENDING初始化DealStatus”,underlyingError:nil)) - Sushil Sharma
1
@SushilSharma 是的。您的数据包含字符串“PENDING”。您的“PENDING”情况的原始值为“Pending”。将您的原始值改为大写。 - JeremyP
但我需要原始值CamelCased。因为我需要在我的标签中显示原始值。 - Sushil Sharma
2
@SushilSharma 要么将您的数据转换为驼峰式,要么为“枚举”创建自定义“描述”,以返回“显示格式”的值。或者实现init(from:)来进行转换。 - JeremyP

4
错误表明该类的某些属性不符合Decodable协议。
将Decodable添加到枚举中,问题就会得到解决。
extension DealStatus: Decodable { }

Codable => Decodable - JeremyP
是的,可解码的。谢谢。 - Sandeep
@Sandeep 感谢您的回答。现在我在解析时遇到了以下错误: 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

2
根据Federico Zanetello在他的文章Swift 4 Decodable: Beyond The Basics中所述,如果您需要解析原语的子集(字符串、数字、布尔值等),Codable和Decodable协议将运行良好。
在您的情况下,只需使DealStatus符合Decodable(如JeremyP建议的那样)即可解决您的问题。您可以在Playgrounds上创建自己的JSON数据并尝试解析它:
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)")
}

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

然而,要成为一名更好的程序员,你应该始终保持好奇心并尝试学习事物的运作方式。如果你对如何符合可解码协议(比如说你需要自定义键、自定义错误或一些更复杂的数据结构)感兴趣,那么你可以这样做:
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)")
}

在这种情况下,输出仍然相同:
ActiveDeals(keyword: "Some keyword", bookingType: "A type", expiryDate: 123456, createdAt: nil, shopLocation: nil, dealStatus: __lldb_expr_67.DealStatus.DECLINED, startingDate: Optional(789456))
DECLINED

感谢您提供详细的答案,对我帮助很大。 - Sushil Sharma

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接