解码包含布尔值的枚举

Posted

技术标签:

【中文标题】解码包含布尔值的枚举【英文标题】:Decoding an enum containing bools 【发布时间】:2019-11-05 01:15:43 【问题描述】:

我有这个 JSON 文件。

[
    
        "name": "January",
        "holidays": [
            
                "name": "New Year's Day",
                "date": "2019-01-01T00:00:00-0500",
                "type": 
                    "isNationalHoliday": true,
                    "isRegionalHoliday": true,
                    "isPublicHoliday": true,
                    "isGovernmentHoliday": true
                
            ,
            
                "name": "Martin Luther King Day",
                "date": "2019-01-21T00:00:00-0500",
                "type": 
                    "isNationalHoliday": true,
                    "isRegionalHoliday": true,
                    "isPublicHoliday": true,
                    "isGovernmentHoliday": true
                
            
        ]
    ,
    
        "name": "February",
        "holidays": [
            
                "name": "Presidents' Day",
                "date": "2019-02-18T00:00:00-0500",
                "type": 
                    "isNationalHoliday": false,
                    "isRegionalHoliday": true,
                    "isPublicHoliday": false,
                    "isGovernmentHoliday": false
                
            
        ]
    ,
    
        "name": "March",
        "holidays": null
    
]

我创建了一个 Month 结构来解码 JSON 中的字典。

public struct Month 
    public let name: String
    public let holidays: [Holiday]?


extension Month: Decodable  

还有一个 Year 结构来包含它们。

public struct Year 
    public let months: [Month]


extension Year: Decodable 
    public init(from decoder: Decoder) throws 
        let container = try decoder.singleValueContainer()
        let values = try container.decode([Month].self)

        months = values
    

我的Holiday 结构有点复杂,因为有一个名为HolidayType 的枚举,我想在其中解码JSON 中type 字段下的值。

public struct Holiday 
    public let name: String
    public let date: Date
    public let type: HolidayType


extension Holiday: Decodable  

public enum HolidayType 
    case isNationalHoliday
    case isRegionalHoliday
    case isPublicHoliday
    case isGovernmentHoliday

    enum CodingKeys: String, CodingKey 
        case isNationalHoliday
        case isRegionalHoliday
        case isPublicHoliday
        case isGovernmentHoliday
    


extension HolidayType: Decodable 
    public init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self = try container.decode(HolidayType.self, forKey: .isNationalHoliday)
        self = try container.decode(HolidayType.self, forKey: .isRegionalHoliday)
        self = try container.decode(HolidayType.self, forKey: .isPublicHoliday)
        self = try container.decode(HolidayType.self, forKey: .isGovernmentHoliday)
    

这是我加载文件和解码的地方。

if let url = Bundle.main.url(forResource: "holidays", withExtension: "json") 
    do 
        let data = try Data(contentsOf: url)
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        let year = try decoder.decode(Year.self, from: data)
        print(year.months)
     catch let error 
        print("Error occurred decoding JSON: \(error)")
    
 else 
    print("Error occurred loading file")

但它失败并出现以下错误。

typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [_JSONKey(stringValue: "索引 0", intValue: 0), CodingKeys(stringValue: "holidays", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0), CodingKeys(stringValue: "type", intValue: nil), CodingKeys(stringValue: "isNationalHoliday", intValue: nil)], debugDescription: "预计解码 字典但找到了一个数字。”,基础错误: 无))

我不知道如何解决这个问题。我还上传了一个演示项目here。

【问题讨论】:

除了下面提到的枚举问题,注意顶层对象是一个数组,所以要解码[Month].self而不是Year.self @MartinR 他有Year的自定义解码。 您的Year 结构是多余的,只需解码let months = try decoder.decode([Month].self, from: data); print(months)。而仅采用Decodableextensions 也是多余的。如果您不是结构或类的所有者,则仅采用协议的空扩展才有意义 @Sulthan:你说得对,我没注意到。 @Sulthan 恕我直言,如果有另一个属性 namenumber 但不要在层次结构的顶层包装单个属性是有意义的。 【参考方案1】:

"isNationalHoliday", intValue: nil)], debugDescription: "本应解码 Dictionary 但找到了一个数字。", underlyingError: nil))

isNationalHoliday 是 bool 值而不是枚举类型,isRegionalHoliday, isPublicHoliday, isGovernmentHoliday 以此类推

你需要

// MARK: - Element
struct Root: Codable 
    let name: String
    let holidays: [Holiday]?


// MARK: - Holiday
struct Holiday: Codable 
    let name: String
    let date: Date
    let type: TypeClass


// MARK: - TypeClass
struct TypeClass: Codable 
    let isNationalHoliday, isRegionalHoliday, isPublicHoliday, isGovernmentHoliday: Bool


let year = try decoder.decode([Root].self, from: data)

【讨论】:

【参考方案2】:

您不能使用枚举来表示多个布尔值。如果你想保持你的类型不变,我建议使用带有自定义解码的OptionSet

struct Year: Decodable 
    let months: [Month]

    init(from decoder: Decoder) throws 
        let container = try decoder.singleValueContainer()
        months = try container.decode([Month].self)
    


struct Month: Decodable 
    let name: String
    let holidays: [Holiday]?


struct Holiday: Decodable 
    let name: String
    let date: Date
    let type: HolidayType


struct HolidayType: OptionSet, Decodable 
    let rawValue: Int

    static let national = HolidayType(rawValue: 1 << 0)
    static let regional = HolidayType(rawValue: 1 << 1)
    static let `public` = HolidayType(rawValue: 1 << 2)
    static let government = HolidayType(rawValue: 1 << 3)

    init(rawValue: Int) 
        self.rawValue = rawValue
    

    init(from decoder: Decoder) throws 
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self = try CodingKeys.allCases
            .filter  try container.decode(Bool.self, forKey: $0) 
            .map  $0.type 
            .reduce([] as HolidayType)  $0.union($1) 
    

    private enum CodingKeys: String, CodingKey, CaseIterable 
        case isNationalHoliday
        case isRegionalHoliday
        case isPublicHoliday
        case isGovernmentHoliday

        var type: HolidayType 
            switch self 
            case .isNationalHoliday:
                return .national
            case .isRegionalHoliday:
                return .regional
            case .isPublicHoliday:
                return .public
            case .isGovernmentHoliday:
                return .government
            
        
    

或者,您可以使用计算变量转换您的类型,而不是自定义解析:

struct Holiday: Decodable 
    let name: String
    let date: Date
    private let type: HolidayTypeHolder
    var types: [HolidayType] 
        return type.types
    


enum HolidayType: String 
    case national, regional, `public`, `government`


private struct HolidayTypeHolder: Decodable 
    let isNationalHoliday, isRegionalHoliday, isPublicHoliday, isGovernmentHoliday: Bool

    var types: [HolidayType] 
        var types: [HolidayType] = []
        if isNationalHoliday 
            types.append(.national)
        
        if isRegionalHoliday 
            types.append(.regional)
        
        if isPublicHoliday 
            types.append(.public)
        
        if isGovernmentHoliday 
            types.append(.government)
        

        return types
    

【讨论】:

谢谢!使用OptionSets 对我来说是一个全新的领域。从来不知道你可以让它们表现得像枚举。 @Isuru 更准确地说,你可以让他们表现得像Set&lt;enum&gt;。声明枚举 HolidayType 然后使用 Set&lt;HolidayType&gt; 作为类型没有太大区别。

以上是关于解码包含布尔值的枚举的主要内容,如果未能解决你的问题,请参考以下文章

左或右应表示为布尔值还是枚举[关闭]

从 NSDictionary 获取包含 NSNumber 布尔值的键列表,其中值为 @YES

用于存储布尔值的 MySQL 数据类型

用于存储布尔值的 MySQL 数据类型

用于存储布尔值的 MySQL 数据类型

将 int 转换为布尔值的更好方法