Декодирование массива JSON без имени поля

У меня есть простой файл 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?

Это помогает? stackoverflow.com/questions/44655562/…

cwa 22.06.2019 10:45

Честно говоря, это не лучшая структура JSON. Вы можете изменить это? Название месяца должно быть значением, а не ключом.

Paulw11 22.06.2019 10:54

Структура месяца недействительна.

Mojtaba Hosseini 22.06.2019 11:02

@cwa Спасибо за ответ. К сожалению, мне нужно включить все поля, а не исключать их.

Isuru 22.06.2019 11:06

@MojtabaHosseini Я проверил с помощью онлайн-валидатора, и он не выдал никаких ошибок.

Isuru 22.06.2019 11:07

@ Paulw11 Хм... понятно. Я могу его изменить. Что-то вроде это будет лучше?

Isuru 22.06.2019 11:07

Да, это было бы лучше.

Paulw11 22.06.2019 11:19
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
7
744
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ваша структура 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 22.06.2019 11:10

@Isuru Так намного лучше!

Sweeper 22.06.2019 11:11

Попался! Я изменю структуру JSON и попробую еще раз.

Isuru 22.06.2019 11:13

Хорошо, я изменил структуру JSON на эту и заменил структуру HolidayData на структуру Year. У него есть только одно свойство, называемое months типа [Month]. Теперь мне нужно вручную декодировать словари месяцев в методе init структуры Year, верно? Но мой вопрос: как определить CodingKey для массива месяцев, когда в JSON нет поля с именем months?

Isuru 22.06.2019 11:34

@Isuru Я бы объявил HolidayData псевдонимом типа [Month], а Month имел бы свойства month: String и holidays: [Holiday].

Sweeper 22.06.2019 11:43

Я посмотрю на это. Спасибо. Я обновил свой вопрос, чтобы включить все новые детали, кстати.

Isuru 22.06.2019 11:51

@Isuru Ваше обновление вопроса делает недействительными многие существующие здесь ответы ... Не могли бы вы отменить свое редактирование и опубликовать новый вопрос? Также было бы неплохо, если бы вы могли принять ответ здесь.

Sweeper 22.06.2019 11:54

Понимаю. Есть ли способ увидеть мои предыдущие правки здесь? Я не могу найти его.

Isuru 22.06.2019 11:56

Структура месяца не соответствует 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). Теперь у меня новая проблема. Я обновил свой исходный вопрос выше.

Isuru 22.06.2019 11:46
Ответ принят как подходящий

Если вы хотите проанализировать 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 22.06.2019 12:16

@Isuru Никогдаprint(error.localizedDescription) в блоке JSONDecoder catch. Всегда print(error). Он точно скажет вам, что не так.

vadian 22.06.2019 12:31

@вадиан Понял! Исправлена ​​проблема. Огромное спасибо вам обоим.

Isuru 22.06.2019 12:37

Другие вопросы по теме