Search code examples
jsonswiftjsondecoder

Swift - Decode array of an arrays JSON data


I'm using Swift 5 and I'm trying to create a struct to hold the contents of an Google Sheets API Call. I'm stuck with "values" key which values i want to fetch, change to Int type and store at separate array variable which i can use lately.

Here's one result from the API:

{
 "range": "Sheet1!A2:B4",
 "majorDimension": "ROWS",
 "values": [
   [
     "-10",
     "12"
   ],
   [
     "-9",
     "-15"
   ],
   [
     "-8",
     "-9"
   ]
   [
     "-7",
     "4"
   ]
 ]
}

In my previous approaches i got an error: "Expected to decode String but found an array instead."

So my question is how should inner structure for "values" looks to finished the task?

struct Sheet: Decodable {
    let range: String?
    let majorDimension: String?
    let values: [Values]?  
}

do {
   let json = try JSONDecoder().decode(Sheet.self, from: data)

  } catch let error {
      print(error as Any)
  }

Thanks!


Solution

  • Note that your JSON is missing a comma after this array:

    [
     "-8",
     "-9"
    ]
    

    Assuming that you fixed that, you need to make the type of values [[String]]?:

    struct Response: Codable {
        // you don't actually need optional properties if you are sure they exist
        let range: String?
        let majorDimension: String?
        let values: [[String]]?
    
        // you don't need CodingKeys here since all your property names match the JSON keys
    }
    

    If you want the numbers as Doubles, you can do this (assuming always-valid numbers):

    struct Response: Codable {
        let range: String?
        let majorDimension: String?
        let values: [[Double]]?
    
        // now you need CodingKeys, but you don't need to give them raw values
        enum CodingKeys: String, CodingKey {
            case range
            case majorDimension
            case values
        }
    
        init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
            range = try container.decodeIfPresent(String.self, forKey: .range)
            majorDimension = try container.decodeIfPresent(String.self, forKey: .majorDimension)
            // use map to transform the strings to doubles
            values = try container.decodeIfPresent([[String]].self, forKey: .values)?
                .map { $0.map { Double($0)! } }
                // or if you want to filter out the invalid numbers...
                // .map { $0.compactMap(Double.init) }
        }
    }