【问题标题】:Decodable conformance with property of enum type与枚举类型的属性的可解码一致性
【发布时间】:2018-07-24 19:35:26
【问题描述】:

我有这个枚举:

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”的初始化程序“init(from:)” (Swift.Decodable)

无法自动合成“可解码” 因为“DealStatus”不符合“Decodable”

【问题讨论】:

    标签: swift struct enums decodable


    【解决方案1】:

    根据 Federico Zanetello 在他的帖子 Swift 4 Decodable: Beyond The Basics 中的说法,如果您需要解析原语子集(字符串、数字、布尔值等),Codable 和 Decobable 协议将可以正常工作。

    在您的情况下,只需使 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
    

    但是,要成为更好的程序员,您应该始终保持好奇心并尝试了解事物的原理,因此,如果您对如何遵守 Decodable 协议感兴趣(假设您需要自定义键、自定义错误或更复杂的数据结构),您可以这样做:

    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
    

    【讨论】:

    • 感谢您的详细回答。对我帮助很大。
    【解决方案2】:

    错误表示类的某些属性不符合可解码协议。

    将 Decodable 一致性添加到您的枚举中应该没问题。

    extension DealStatus: Decodable { }
    

    【讨论】:

    • Codable => Decodable
    • 是的,可解码。谢谢
    • @Sandeep 感谢您的回答。现在我在解析时遇到此错误: dataCorrupted(Swift.DecodingError.Context(codingPath: [O3.ActiveDealsContent.(CodingKeys in _300B97B81632ECD4F45F86E6B039153F).content, Foundation.(_JSONKey in _12768CA107A31EF2DCE034FD75B541C9)(stringValue: "Index 0," (0)), O3.ActiveDeals.(CodingKeys in _300B97B81632ECD4F45F86E6B039153F).dealStatus], debugDescription: "Cannot initialize DealStatus from invalid String value PENDING", underlyingError: nil))
    【解决方案3】:

    问题是 Swift 可以自动合成 Decodable 所需的方法,前提是结构的所有属性也是 Decodable 并且您的枚举不是 Decodable

    刚刚在操场上试了一下,看来你可以让你的枚举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," (0)), O3.ActiveDeals.(CodingKeys in _300B97B81632ECD4F45F86E6B039153F).dealStatus], debugDescription: "Cannot initialize DealStatus from invalid String value PENDING", underlyingError: nil))
    • @SushilSharma 是的。您的数据包含字符串PENDINGPENDING 案例的原始值是 Pending。将原始值设为大写。
    • 但我需要原始值 CamelCased。因为我需要在我的一个标签中显示原始值。
    • @SushilSharma 然后将您的数据设为驼峰式大小写或为enum 创建自定义description,以“显示格式”返回值。或者实现init(from:)进行转换。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-08
    • 2020-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多