解码没有字段名称的JSON数组

时间:2019-06-22 08:38:00

标签: ios json swift swift4 jsondecoder

我有一个像这样的简单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
}

但是它始终失败,并出现以下错误。

  

由于数据格式不正确,因此无法读取。

我猜测它失败了,因为即使holidays结构中有一个字段,JSON文件中也没有名为Month的字段。

如何将holidays数组添加到holidays字段中而不将其包含在JSON中?

3 个答案:

答案 0 :(得分:1)

您的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中的键名称具有不同的名称。错字?

答案 1 :(得分:1)

如果要在不编写自定义解码逻辑的情况下解析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)

为此,我不得不制作isBankHolidayisMercantileHoliday Optional,因为它们并不总是出现在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)

答案 2 :(得分:0)

月份结构与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
    ]
}