У меня есть простой файл JSON, подобный этому.
{
"january": [
{
"name": "New Year's Day",
"date": "2019-01-01T00:00:00-0500",
"isNationalHoliday": true,
"isRegionalHoliday": true,
"isPublicHoliday": true,
"isGovernmentHoliday": true
},
{
"name": "Martin Luther King Day",
"date": "2019-01-21T00:00:00-0500",
"isNationalHoliday": true,
"isRegionalHoliday": true,
"isPublicHoliday": true,
"isGovernmentHoliday": true
}
],
"february": [
{
"name": "Presidents' Day",
"date": "2019-02-18T00:00:00-0500",
"isNationalHoliday": false,
"isRegionalHoliday": true,
"isPublicHoliday": false,
"isGovernmentHoliday": false
}
],
"march": null
}
Я пытаюсь использовать Swift JSONDecoder для декодирования их в объекты. Для этого я создал объект Month и Holiday.
public struct Month {
public let name: String
public let holidays: [Holiday]?
}
extension Month: Decodable { }
public struct Holiday {
public let name: String
public let date: Date
public let isNationalHoliday: Bool
public let isRegionalHoliday: Bool
public let isPublicHoliday: Bool
public let isGovernmentHoliday: Bool
}
extension Holiday: Decodable { }
И отдельная модель HolidayData для хранения всех этих данных.
public struct HolidayData {
public let months: [Month]
}
extension HolidayData: Decodable { }
Здесь я занимаюсь расшифровкой.
guard let url = Bundle.main.url(forResource: "holidays", withExtension: "json") else { return }
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let jsonData = try decoder.decode(Month.self, from: data)
print(jsonData)
} catch let error {
print("Error occurred loading file: \(error.localizedDescription)")
return
}
Но он продолжает терпеть неудачу со следующей ошибкой.
The data couldn’t be read because it isn’t in the correct format.
Я предполагаю, что это не удается, потому что в файле JSON нет поля с именем holidays, хотя оно есть в структуре Month.
Как добавить массив праздников в поле holidays, не имея его в JSON?
Честно говоря, это не лучшая структура JSON. Вы можете изменить это? Название месяца должно быть значением, а не ключом.
Структура месяца недействительна.
@cwa Спасибо за ответ. К сожалению, мне нужно включить все поля, а не исключать их.
@MojtabaHosseini Я проверил с помощью онлайн-валидатора, и он не выдал никаких ошибок.
@ Paulw11 Хм... понятно. Я могу его изменить. Что-то вроде это будет лучше?
Да, это было бы лучше.





Ваша структура JSON довольно неудобна для декодирования, но это можно сделать.
Ключевым моментом здесь является то, что вам нужно перечисление CodingKey, подобное этому (каламбур):
enum Months : CodingKey, CaseIterable {
case january
case feburary
case march
// ...
}
И вы можете предоставить собственную реализацию init(decoder:) в своей структуре HolidayData:
extension HolidayData : Decodable {
public init(from decoder: Decoder) throws {
var months = [Month]()
let container = try decoder.container(keyedBy: Months.self)
for month in Months.allCases {
let holidays = try container.decodeIfPresent([Holiday].self, forKey: month)
months.append(Month(name: month.stringValue, holidays: holidays))
}
self.months = months
}
}
Также обратите внимание, что имена свойств ваших структур отличаются от имен ключей в вашем JSON. Опечатка?
Спасибо за ответ :) Понятно. Мне тоже кто-то говорил о проблеме со структурой JSON, могу поменять. Как насчет чего-то вроде это?
@Isuru Так намного лучше!
Попался! Я изменю структуру JSON и попробую еще раз.
Хорошо, я изменил структуру JSON на эту и заменил структуру HolidayData на структуру Year. У него есть только одно свойство, называемое months типа [Month]. Теперь мне нужно вручную декодировать словари месяцев в методе init структуры Year, верно? Но мой вопрос: как определить CodingKey для массива месяцев, когда в JSON нет поля с именем months?
@Isuru Я бы объявил HolidayData псевдонимом типа [Month], а Month имел бы свойства month: String и holidays: [Holiday].
Я посмотрю на это. Спасибо. Я обновил свой вопрос, чтобы включить все новые детали, кстати.
@Isuru Ваше обновление вопроса делает недействительными многие существующие здесь ответы ... Не могли бы вы отменить свое редактирование и опубликовать новый вопрос? Также было бы неплохо, если бы вы могли принять ответ здесь.
Понимаю. Есть ли способ увидеть мои предыдущие правки здесь? Я не могу найти его.
Структура месяца не соответствует json.
Измените структуру месяца на что-то вроде этого:
public struct Year {
public let January: [Holyday]?
public let February: [Holyday]?
public let March: [Holyday]?
public let April: [Holyday]?
public let May: [Holyday]?
public let June: [Holyday]?
public let July: [Holyday]?
public let August: [Holyday]?
public let September: [Holyday]?
public let October: [Holyday]?
public let November: [Holyday]?
public let December: [Holyday]?
}
extension Year: Decodable { }
Обратите внимание, что это не лучшая практика того, как вы можете достичь того, чего хотите.
Другой путь — изменить json (если у вас есть доступ) в соответствии с вашими структурами:
{[
"name":"january",
"holidays": [
{
"name": "New Year's Day",
"date": "2019-01-01T00:00:00-0500",
"isNationalHoliday": true,
"isRegionalHoliday": true,
"isPublicHoliday": true,
"isGovernmentHoliday": true
},
{
"name": "Martin Luther King Day",
"date": "2019-01-21T00:00:00-0500",
"isNationalHoliday": true,
"isRegionalHoliday": true,
"isPublicHoliday": true,
"isGovernmentHoliday": true
}
]],[
"name":"february",
"holidays": [
{
"name": "Presidents' Day",
"date": "2019-02-18T00:00:00-0500",
"isNationalHoliday": false,
"isRegionalHoliday": true,
"isPublicHoliday": false,
"isGovernmentHoliday": false
}
]],[
"name":"march",
"holidays": null
]
}
На самом деле я изменил свой JSON, чтобы он выглядел именно так (поле month вместо name). Теперь у меня новая проблема. Я обновил свой исходный вопрос выше.
Если вы хотите проанализировать JSON без написания пользовательской логики декодирования, вы можете сделать это следующим образом:
public struct Holiday: Decodable {
public let name: String
public let date: Date
public let isBankHoliday: Bool?
public let isPublicHoliday: Bool
public let isMercantileHoliday: Bool?
}
try decoder.decode([String: [Holiday]?].self, from: data)
Для этого мне пришлось сделать isBankHoliday и isMercantileHolidayOptional, так как они не всегда появляются в JSON.
Теперь, если вы хотите декодировать его в структуру, которую вы представили выше, вам придется написать собственную логику декодирования:
public struct Month {
public let name: String
public let holidays: [Holiday]?
}
extension Month: Decodable { }
public struct Holiday {
public let name: String
public let date: Date
public let isBankHoliday: Bool
public let isPublicHoliday: Bool
public let isMercantileHoliday: Bool
enum CodingKeys: String, CodingKey {
case name
case date
case isBankHoliday
case isPublicHoliday
case isMercantileHoliday
}
}
extension Holiday: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
name = try container.decode(String.self, forKey: .name)
date = try container.decode(Date.self, forKey: .date)
isBankHoliday = try container.decodeIfPresent(Bool.self, forKey: .isBankHoliday) ?? false
isPublicHoliday = try container.decodeIfPresent(Bool.self, forKey: .isPublicHoliday) ?? false
isMercantileHoliday = try container.decodeIfPresent(Bool.self, forKey: .isMercantileHoliday) ?? false
}
}
public struct HolidayData {
public let months: [Month]
}
extension HolidayData: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let values = try container.decode([String: [Holiday]?].self)
months = values.map { (name, holidays) in
Month(name: name, holidays: holidays)
}
}
}
decoder.decode(HolidayData.self, from: data)
Спасибо за подробный ответ :) Однако я получаю новую ошибку Данные не могут быть прочитаны, потому что они отсутствуют. Я загрузил демонстрационный проект здесь, если вы могли бы взглянуть.
@Isuru Никогдаprint(error.localizedDescription) в блоке JSONDecoder catch. Всегда print(error). Он точно скажет вам, что не так.
@вадиан Понял! Исправлена проблема. Огромное спасибо вам обоим.
Это помогает? stackoverflow.com/questions/44655562/…