【问题标题】:Decoding an enum containing bools解码包含布尔值的枚举
【发布时间】:2023-03-04 18:58:01
【问题描述】:

我有这个 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 但不要在层次结构的顶层包装单个属性是有意义的。

标签: ios json swift boolean jsonencoder


【解决方案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; 作为类型没有太大区别。
    猜你喜欢
    • 1970-01-01
    • 2011-05-19
    • 2011-05-10
    • 2012-10-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多