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!

1
  • 1
    you have an error - comma is missing after "-9"], also you can try service like app.quicktype.io to validate and parse JSON in any language Commented Mar 15, 2020 at 20:09

2 Answers 2

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) }
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

You could use compactMap instead of the last map to avoid the forced unwrap
@JoakimDanielson Good idea. Added as another option.
@JoakimDanielson Actually, would that make sense though? This is a spreadsheets, so would not expect the 2D array to have arrays with unequal lengths.
Ah, maybe not. I didn't really pay attention to that this was data from a spreadsheet.
@SimbaNew Did you write let values: [[Double]]?, and remember the ? on the end of the previous line.
|
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) }
     }
   }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.