3

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!

SimbaNew
  • 33
  • 4
  • 1
    you have an error - comma is missing after "-9"], also you can try service like https://app.quicktype.io/ to validate and parse JSON in any language – moonvader Mar 15 '20 at 20:09

2 Answers2

1

The JSON you've posted is invalid (missing a comma), but when you fix that it would be parseable when using

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

i.e. by making values be a two-dimensional array of Strings.

To convert the values to the required Int values, you could provide an accessor:

extension Sheet {
   var intValues: [[Int]] {
     return values.map {
       $0.compactMap { Int($0) }
     }
   }
}
Gereon
  • 17,258
  • 4
  • 42
  • 73
1

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) }
    }
}
Sweeper
  • 213,210
  • 22
  • 193
  • 313