0

I am trying to use specific capabilities not available in the available iOS SDK for Parse Platform (Server), but that I know are available with the REST API. Specifically to use a DISTINCT query.

Using the Parse Dashboard and REST API client app on my dev computer (Rested.app), I have verified the following query completes as expected:

curl -X GET \
-H "X-Parse-Application-Id: someAppID" \
-H "X-Parse-Master-Key: someKey" \
-G \
--data-urlencode "distinct=TeeTime" \
http://somehost:1337/parse/aggregate/CompEntry

Which successfully returns data:

{ "results": [ { "__type": "Date", "iso": "2020-08-29T07:00:00.000Z" }, { "__type": "Date", "iso": "2020-08-29T07:09:00.000Z" } ] }

The original data is from, which has 3 rows, 2 of which share the same TeeTime:

Parse Dashboard screenshot

And a screenshot of the output from the Rested.app:

Rested.app Output screenshot

Now I am trying to convert this for my Swift / iOS project.

I am trying to move the downloaded data into a new struct to represent the object(s), using the Codable/Decodable approach and matching the JSON property names. The code I have so far is below (placed some comments inline too). The Struct definitions occur in separate .swift files, but so long as outside the main ViewController definition.

struct TeeTimeData: Codable {
    let results: [Results]
}

struct Results: Codable {
    let __type: String
    let iso: String // TODO: THIS SHOULD BE A DIFFERENT DATA TYPE - I HAVE PICKED HARDER DATA TYPE TO START WITH!
}

Then within the main ViewController struct:

class ViewController: UIViewController {

    @IBAction func buttonGetTeeTimes(_ sender: UIButton) {
        
        if let url = URL(string: "http://somehost:1337/parse/aggregate/CompEntry") {
            var request = URLRequest(url: url)
            request.addValue("someAppID", forHTTPHeaderField: "X-Parse-Application-Id")
            request.addValue("someKey", forHTTPHeaderField: "X-Parse-Master-Key")
            request.httpMethod = "GET"
            let params = ["distinct": "TeeTime"] // TODO: THIS VAR IS NOT ACTUALLY BEING TIED TO THE REQUEST EITHER - 2nd PROBLEM...
            
            let session = URLSession(configuration: .default)
            
            let requestTask = session.dataTask(with: request) { (data, response, error) in
                
                if error != nil {
                    print("API Error: \(error)")
                }
                
                if let dataUnwrap = data {
                    // TODO: MOVE THIS INTO NEW CLASS (DataModel, etc)
                    let decoder = JSONDecoder()
                    
                    do {
                        let decodedData = try decoder.decode(TeeTimeData.self, from: dataUnwrap)
                        
                        print(decodedData)
                        
                    } catch {
                        print("Decode Error: \(error)")
                    }
                    
                    
                }
            }
            
            requestTask.resume()
            
        }
    }
}

And the console output is:

Decode Error: keyNotFound(CodingKeys(stringValue: "__type", intValue: nil), Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "results", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "No value associated with key CodingKeys(stringValue: "__type", intValue: nil) ("__type").", underlyingError: nil))

My first guess is the 2 underscores, "__type", at the start of the property definition?

jingo_man
  • 509
  • 3
  • 13
  • There is no key `__type` in your response JSON. – gcharita Sep 02 '20 at 13:26
  • `print("Decode Error: \(error)")` => `print("Decode Error: \(error)"); print("With response: \(String(data: data, encoding: .utf8))")`might help. – Larme Sep 02 '20 at 13:28
  • The *dummy data* doesn't cause that error, so the real data must be different. – vadian Sep 02 '20 at 13:50
  • @gcharita - I thought the returned JSON at the top-level is called "results" that consists of an array of sub-items, each of which has 2 properties (__type and iso). Is that incorrect? – jingo_man Sep 02 '20 at 14:37
  • @Larme, I would like to convert the returned data into Swift struct's not string manipulation – jingo_man Sep 02 '20 at 14:38
  • No, that's for debugging proposes. To see you real data, and might see why it fails while it didn't on your tests. – Larme Sep 02 '20 at 14:39
  • @vadian - by "dummy data" I mean the actual data that is stored on my Parse Platform instance. But its dummy data to prove the app / code logic. There are 3 rows of data entered into this class (table), but 2 of them share the same TeeTime, therefore only 2 items are returned. – jingo_man Sep 02 '20 at 14:39
  • 1
    `"--data-urlencode "distinct=TeeTime"` Doesn't it means that it'd add `&distinct=TeeTime` to the URL? You aren't doing it in your code, no? So maybe that's why the response is different (and why you might want to show the returned data as String). – Larme Sep 02 '20 at 14:41
  • It doesn't seem to be the same thing. the `curl` statement provided is copy/pasted from an "Export" button provided on the Parse Dashboard – jingo_man Sep 02 '20 at 14:42
  • @jingo_man no, if your response JSON is like your dummy one, your model is correct. – gcharita Sep 02 '20 at 14:43
  • I tried the String() manipulation, which returns ALL 3 rows but includes all information from each row, not filtered as I need from the `distinct` filter/query, because in my case I also haven't been able to apply the "distinct=TeeTime" portion (which does not seem to be passed in as a parameter as suggested by @Larme, but with the '-G' curl flag) – jingo_man Sep 02 '20 at 14:58
  • You can construct the url with `QueryComponents`, manually just to test your parsing, you should be able to write the URL `"http://somehost:1337/parse/aggregate/CompEntry&distinct=TeeTime` instead. – Larme Sep 02 '20 at 15:01
  • If I try `&distinct=TeeTime` appended to the URL, in the Rested.app output, all I get is: `{ "results": [] }` – jingo_man Sep 02 '20 at 15:05
  • 1
    I meant `?distinct=TeeTime`, sorry, typo. Check https://en.wikipedia.org/wiki/Query_string and https://developer.apple.com/documentation/foundation/urlqueryitem (and related StackOverflow questions if needed) to build it with your parameters. – Larme Sep 02 '20 at 15:07
  • Yes, `?distinct=TeeTime` to the end of the URL string works! And explains the problem I was experiencing. Without this filter/query, it was returning a different JSON structure, so it wasn't decoding into my TeeTimeData struct format. It does so now: `Decoded data: TeeTimeData(results: [Parse_2.Results(__type: "Date", iso: "2020-08-29T07:00:00.000Z"), Parse_2.Results(__type: "Date", iso: "2020-08-29T07:09:00.000Z")])`. Many thanks, @Larme – jingo_man Sep 02 '20 at 16:10

1 Answers1

0

Answer provided by @Larme in the comments to the opening post. Contributions from @gcharita that my syntax should have worked if it was the same structure as the other tests I was performing (it wasn't - see below).

The error was occurring because the results being returned was a different JSON structure because the distinct=TeeTime filter wasn't being applied. So it was returning a JSON object with all the rows of data from the class, not those specified in my TeeTimeData struct object.

Appending ?distinct=TeeTime to the end of the URL string resolved the issue and returns:

Decoded data: TeeTimeData(results: [Parse_2.Results(__type: "Date", iso: "2020-08-29T07:00:00.000Z"), Parse_2.Results(__type: "Date", iso: "2020-08-29T07:09:00.000Z")])

It also means I no longer require the let params = ["distinct": "TeeTime"] code as well.

jingo_man
  • 509
  • 3
  • 13
  • Keep the params, just use `URLQueryItems`: https://stackoverflow.com/questions/34060754/how-can-i-build-a-url-with-query-parameters-containing-multiple-values-for-the-s – Larme Sep 02 '20 at 16:20