diff --git a/spec/ParseServerRESTController.spec.js b/spec/ParseServerRESTController.spec.js index 31d1f5aec7..85a5867910 100644 --- a/spec/ParseServerRESTController.spec.js +++ b/spec/ParseServerRESTController.spec.js @@ -2,6 +2,7 @@ const ParseServerRESTController = require('../lib/ParseServerRESTController') .ParseServerRESTController; const ParseServer = require('../lib/ParseServer').default; const Parse = require('parse/node').Parse; +const request = require('../lib/request'); let RESTController; @@ -675,4 +676,56 @@ describe('ParseServerRESTController', () => { const result = await Parse.Push.getPushStatus(pushStatusId); expect(result.id).toBe(pushStatusId); }); + + it('should not convert undefined values to null on update with directAccess', async () => { + const createRes = await RESTController.request('POST', '/classes/MyObject', { + presentField: 'hello', + }); + expect(createRes.objectId).toBeDefined(); + + await RESTController.request('PUT', `/classes/MyObject/${createRes.objectId}`, { + presentField: 'updated', + absentField: undefined, + }); + + const getRes = await RESTController.request('GET', `/classes/MyObject/${createRes.objectId}`); + + expect(getRes.presentField).toBe('updated'); + expect(getRes.absentField).toBeUndefined(); + expect('absentField' in getRes).toBe(false); + }); + + it('should not convert undefined values to null on update without directAccess (HTTP mode)', async () => { + const serverURL = 'http://localhost:8378/1'; + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-Master-Key': Parse.masterKey, + }; + + const createRes = await request({ + method: 'POST', + headers, + url: `${serverURL}/classes/MyObject`, + body: { presentField: 'hello' }, + }); + expect(createRes.data.objectId).toBeDefined(); + + await request({ + method: 'PUT', + headers, + url: `${serverURL}/classes/MyObject/${createRes.data.objectId}`, + body: { presentField: 'updated', absentField: undefined }, + }); + + const getRes = await request({ + method: 'GET', + headers, + url: `${serverURL}/classes/MyObject/${createRes.data.objectId}`, + }); + + expect(getRes.data.presentField).toBe('updated'); + expect(getRes.data.absentField).toBeUndefined(); + expect('absentField' in getRes.data).toBe(false); + }); }); diff --git a/src/ParseServerRESTController.js b/src/ParseServerRESTController.js index 9ec4b6f86e..cc92dbb3bf 100644 --- a/src/ParseServerRESTController.js +++ b/src/ParseServerRESTController.js @@ -29,6 +29,22 @@ function getAuth(options = {}, config) { }); } +function stripUndefined(obj) { + if (obj === null || obj === undefined || typeof obj !== 'object') { + return obj; + } + if (Array.isArray(obj)) { + return obj.map(item => item === undefined ? null : stripUndefined(item)); + } + const result = {}; + for (const key of Object.keys(obj)) { + if (obj[key] !== undefined) { + result[key] = stripUndefined(obj[key]); + } + } + return result; +} + function ParseServerRESTController(applicationId, router) { function handleRequest(method, path, data = {}, options = {}, config) { // Store the arguments, for later use if internal fails @@ -113,7 +129,7 @@ function ParseServerRESTController(applicationId, router) { return new Promise((resolve, reject) => { getAuth(options, config).then(auth => { const request = { - body: data, + body: stripUndefined(data), config, auth, info: {