Skip to content

Select and Exclude queries do not properly digest JSON arrays #7245

Closed
@cbaker6

Description

@cbaker6

New Issue Checklist

Issue Description

The keys and excludeKeys on the server are not digesting actual JSON arrays, ["yolo",...], but instead digesting "yolo,..." JS treats both of these the same (I discuss why below), but the server doesn't like it the way the Swift JSON encoder sends an encoded array for these properties, ["yolo",...]. Note that this is the JSON encoder straight from Apples Swift source code and not the custom one used here for ParseObjects, so if it was encoding arrays incorrectly I'm sure it would have been caught awhile ago as the Apple JSON encoder is used by a ton of people.

At first glance this may appear to be a client side bug as there are currently server-side tests and the JS SDK(select and exclude), Flutter SDK (keysToReturn and excludeKeysToReturn), and the Android SDK (selectedKeys) uses it. The iOS SDK uses selectedKeys, but never sends them to the server, instead it strips the keys not selected after the server returns the ParseObject, so I'll ignore this SDK for the rest of the discussion. Below is why I believe the above SDKs work, but also why their current design (which can remain the same) has led to the problem not being discovered until now:

  • The JS, Flutter, and Android SDKs don't actually encode JSON arrays for select and exclude. Instead, they all join array elements into a string separated by commas before sending to the server, always sending something like, "yolo,..." which isn't an JSON array. This is why their implementations work with the current Parse Server implementation. Conversely, the Parse-Swift SDK uses Apples native JSON encoder and sends ["yolo",...]. When I make the Parse-Swift use a join array like the aforementioned SDKs, it works fine with the current implementation on the server.
  • The changes in the PR doesn't break any of the parse-server test cases which means the JS, Flutter, and Android SDKs will probably not require any changes. If they do end up requiring changes, then it will be a change for the better because they should have originally been sending JSON arrays. Instead, the JS, Flutter, and Android SDKs have essentially adapted to a bug. Example from the REST documentation for sending arrays (notice the "$all":[2,3,4]):
curl -X GET \
  -H "X-Parse-Application-Id: ${APPLICATION_ID}" \
  -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \
  -G \
  --data-urlencode 'where={"arrayKey":{"$all":[2,3,4]}}' \
  https://YOUR.PARSE-SERVER.HERE/parse/classes/RandomObject

You can restrict the fields returned by passing keys or excludeKeys a comma-separated list.

IMO this deviates from purpose of REST and JSON as it suggests manually parsing/creating lists/strings instead of using underlying libraries that encode/decode JSON based on standards. I’ll create a PR to fix this as well

The linked PR has the possibility of being breaking change for some users due to:

This is the test case I removed because it 1) doesn't work anymore 2) not sure why we want this particular functionality for select. If you really want to exclude all keys, there should probably create an excludeAll or an exclude(["*"] (I have sense added back in this feature in the PR, but my thoughts on this are below)

I didn't attempt to make that old test case work anymore, because I don't think the feature should have been there in the first place, but I'm open to discussion on this.

Approach

Make keys and excludeKeys use the same strategy as a similar property like include which actually digests a JSON string array,

if (body.keys) {
options.keys = String(body.keys);
}
if (body.include) {
options.include = String(body.include);
}
if (body.excludeKeys) {
options.excludeKeys = String(body.excludeKeys);
}

Steps to reproduce

  1. Make your favorite client SDK take an array (["yolo", ...]) of string for keys or excludeKeys. You can use the links for each SDK above. You can see an example when the issue was first discovered on Parse-Swift, .select() in finds and firsts does not seem to exclude any fields Parse-Swift#86
  2. Create a select or excludeKey query
  3. Use first or find to query the server

Actual Outcome

All keys will be returned as the server ignores the selected or excludedKeys

Expected Outcome

The server should honor what ever keys you select/exclude in an JSON array

Failing Test Case / Pull Request

  • 🤩 I submitted a PR with a fix and a test case.
  • 🧐 I submitted a PR with a failing test case. Not possible on the server side because the test cases use the JS SDK and I mentioned above JS doesn’t actually send JSON arrays for keys and excludeKeys, instead it joins the array into a comma delineated string on the client side, essentially removing the fact that it’s an array. To write this test, you would have to make the JS SDK send a real array, have that PR approved, bump JS on the server, etc.

Environment

Server

  • Parse Server version: master, 4.5.0
  • Operating system: Docker linux, macOS
  • Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): local

Database

  • System (MongoDB or Postgres): mongo, postgres
  • Database version: 4.4, 13.2
  • Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): local

Client

  • SDK (iOS, Android, JavaScript, PHP, Unity, etc): Parse-Swift
  • SDK version: 1.1.0

Logs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions