greg42627
greg42627

Reputation: 79

Accessing a member of a nested JSON string in Swift 4

I am trying to parse a nested JSON string. Below are the JSON string, the structure I've created, and the JSON decoding function I am using. I am able to access the name member easily, but am having trouble with the other items. For instance, in my employeeData object, how can I access Sam's hours from the 3/31/2018 record?

Thank you in advance.

JSON String

[
   {
      "name": "John",
      "records": [
         {
            "reportDate": "2018-06-30",
            "hours": 204,
            "billable": 32844
         },
         {
            "reportDate": "2018-03-31",
            "hours": 234,
            "billable": 37715
         }
      ]
   },
   {
      "name": "Sam",
      "records": [
         {
            "reportDate": "2018-06-30",
            "hours": 187,
            "billable": 13883
         },
         {
            "reportDate": "2018-03-31",
            "hours": 176,
            "billable": 13467
         }
      ]
   }
]

Struct

struct Employee : Decodable {

let name : String?
let records : [Record]

private enum CodingKeys: String, CodingKey {
    case name = "name"
    case records
}

struct Record : Decodable {

    let reportDate : String?
    let hours : Int?
    let billable : Int?

    private enum CodingKeys: String, CodingKey {
        case reportDate = "reportDate"
        case hours = "hours"
        case billable = "billable"
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        reportDate = try values.decodeIfPresent(String.self, forKey: .reportDate)
        hours = try values.decodeIfPresent(Int.self, forKey: .hours)
        billable = try values.decodeIfPresent(Int.self, forKey: .billable)
    }
}
}

JSON Decoding fiction

func downloadJSON( completed:@escaping ()->()){

guard let qurl = URL("https://website.com") else { return }
URLSession.shared.dataTask(with: qurl) { (data, response, error) in
    if error == nil {
        do{
            self.employeeData = try JSONDecoder().decode([Employee].self, from: data!)

            DispatchQueue.main.async{ completed() }
        } catch { print("JSON Error") }
    }
    }.resume()
}

Upvotes: 1

Views: 592

Answers (1)

vadian
vadian

Reputation: 285069

First of all you can reduce your structs to

struct Employee : Decodable {

    let name : String
    let records : [Record]

    struct Record : Decodable {

        let reportDate : String
        let hours : Int
        let billable : Int
    }
}

The CodingKeys and the initializer are created by the protocol extension.

To get the inner data you need two loops

for employee in self.employeeData {
    print(employee.name)
    for record in employee.records {
        print(record.reportDate)
        print(record.hours)
    }
}

To get Sam's hours at 2018-03-31 you could filter the data with

if let sam = self.employeeData.first(where: {$0.name == "Sam"}),
    let date = sam.records.first(where: {$0.reportDate == "2018-03-31"}) {
    print(date.hours)
}

Upvotes: 1

Related Questions