У меня есть этот файл 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, где я хочу декодировать значения в поле type в JSON.
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: "Index 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: "Expected to decode Dictionary but found a number instead.", underlyingError: nil))
Я не могу понять, как это исправить. Я также загрузил демонстрационный проект здесь.
@MartinR У него есть собственное декодирование для Year.
Ваша структура Year избыточна, просто расшифруйте let months = try decoder.decode([Month].self, from: data); print(months). И extension только для принятия Decodable тоже избыточны. Пустое расширение только для принятия протокола имеет смысл, если вы не являетесь владельцем структуры или класса.
@Sulthan: Ты прав, я этого не заметил.
@vadian Имеет смысл обернуть месяцы в именованный тип. Я не вижу проблемы в правильном именовании типов, это не избыточность.
@Sulthan IMHO имеет смысл, если бы было другое свойство name или number, но не обертывать одно свойство на верхнем уровне иерархии.
@vadian Я постоянно создаю новые типы, просто оборачиваю String (например, AccountNumber или Currency) или Decimal (например, Amount, InterestRate). Вы не знаете, какие еще методы могут быть объявлены для данного типа, и проще передать year: Year вместо year: [Month].
@Sulthan Я понимаю, но (на данный момент) в JSON нет ключа year...
... а структура singleValueContainer происходит от этот ответ предыдущего вопроса ОП, где значение было совершенно другим.





"isNationalHoliday", intValue: nil)], debugDescription: "Expected to decode Dictionary but found a number instead.", underlyingError: nil))
isNationalHoliday - это логическое значение, а не тип перечисления, так что для 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)
Вы не можете использовать перечисление для представления нескольких логических значений. Если вы хотите сохранить свои типы без изменений, я бы рекомендовал использовать 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<enum>. Объявление enum HolidayType и последующее использование Set<HolidayType> для типа не имеет большого значения.
В дополнение к проблемам с перечислением, упомянутым ниже, обратите внимание, что объект верхнего уровня является массивом, поэтому вам нужно декодировать
[Month].self, а неYear.self.