Repository: parse-community/Parse-SDK-JS
Pull request #1294 (2021-02-11 03:01:59)

feat: Add option to return raw json from queries
Mimics [options](https://github.com/parse-community/parse-php-sdk/pull/423) in PHP SDK.

Works for get, find and first queries.

peril-parse-community[bot]: 
<!--
  0 failure: 
  0 warning: 
  
  1 markdown notices
  DangerID: danger-id-Peril;
-->



Danger run resulted in 1 warning; to find out more, see the [checks page](https://github.com/parse-community/Parse-SDK-JS/runs/1876529182).
<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a>
</p>

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1294?src=pr&el=h1) Report
> Merging [#1294](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1294?src=pr&el=desc) (a1c6d41) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/3551a06a94de60e945e2da780cf4a3b3201838da?el=desc) (3551a06) will **not change** coverage.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1294/graphs/tree.svg?width=650&height=150&src=pr&token=8nCENM0QHj)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1294?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##            master     #1294   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           59        59           
  Lines         5788      5794    +6     
  Branches      1299      1302    +3     
=========================================
+ Hits          5788      5794    +6     
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1294?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ParseQuery.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1294/diff?src=pr&el=tree#diff-c3JjL1BhcnNlUXVlcnkuanM=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1294?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1294?src=pr&el=footer). Last update [3551a06...a1c6d41](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1294?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/ParseObject.js b/src/ParseObject.js
index 312428d..6ca88db 100644
--- a/src/ParseObject.js
+++ b/src/ParseObject.js
@@ -1823,10 +1823,11 @@ class ParseObject {
    * @param {object} json The JSON map of the Object's data
    * @param {boolean} override In single instance mode, all old server data
    *   is overwritten if this is set to true
+   * @param {boolean} dirty Whether the Parse.Object should set JSON keys to dirty
    * @static
    * @returns {Parse.Object} A Parse.Object reference
    */
-  static fromJSON(json: any, override?: boolean) {
+  static fromJSON(json: any, override?: boolean, dirty?: boolean) {
     if (!json.className) {
       throw new Error('Cannot create an object without a className');
     }
@@ -1836,6 +1837,9 @@ class ParseObject {
     for (const attr in json) {
       if (attr !== 'className' && attr !== '__type') {
         otherAttributes[attr] = json[attr];
+        if (dirty) {
+          o.set(attr, json[attr]);
+        }
       }
     }
     if (override) {
diff --git a/src/ParseQuery.js b/src/ParseQuery.js
index 62c6329..4f3c358 100644
--- a/src/ParseQuery.js
+++ b/src/ParseQuery.js
@@ -601,6 +601,7 @@ class ParseQuery {
    *   <li>sessionToken: A valid session token, used for making a request on
    *       behalf of a specific user.
    *   <li>context: A dictionary that is accessible in Cloud Code `beforeFind` trigger.
+   *   <li>json: Return raw json without converting to Parse.Object
    * </ul>
    *
    * @returns {Promise} A promise that is resolved with the result when
@@ -619,6 +620,9 @@ class ParseQuery {
     if (options && options.hasOwnProperty('context') && typeof options.context === 'object') {
       firstOptions.context = options.context;
     }
+    if (options && options.hasOwnProperty('json')) {
+      firstOptions.json = options.json;
+    }
 
     return this.first(firstOptions).then(response => {
       if (response) {
@@ -640,6 +644,7 @@ class ParseQuery {
    *   <li>sessionToken: A valid session token, used for making a request on
    *       behalf of a specific user.
    *   <li>context: A dictionary that is accessible in Cloud Code `beforeFind` trigger.
+   *   <li>json: Return raw json without converting to Parse.Object
    * </ul>
    *
    * @returns {Promise} A promise that is resolved with the results when
@@ -686,8 +691,11 @@ class ParseQuery {
         if (select) {
           handleSelectResult(data, select);
         }
-
-        return ParseObject.fromJSON(data, !select);
+        if (options.json) {
+          return data;
+        } else {
+          return ParseObject.fromJSON(data, !select);
+        }
       });
 
       const count = response.count;
@@ -849,6 +857,7 @@ class ParseQuery {
    *   <li>sessionToken: A valid session token, used for making a request on
    *       behalf of a specific user.
    *   <li>context: A dictionary that is accessible in Cloud Code `beforeFind` trigger.
+   *   <li>json: Return raw json without converting to Parse.Object
    * </ul>
    *
    * @returns {Promise} A promise that is resolved with the object when
@@ -900,8 +909,11 @@ class ParseQuery {
       if (select) {
         handleSelectResult(objects[0], select);
       }
-
-      return ParseObject.fromJSON(objects[0], !select);
+      if (options.json) {
+        return objects[0];
+      } else {
+        return ParseObject.fromJSON(objects[0], !select);
+      }
     });
   }
 
diff --git a/src/ParseSchema.js b/src/ParseSchema.js
index c7fa844..49c717f 100644
--- a/src/ParseSchema.js
+++ b/src/ParseSchema.js
@@ -214,6 +214,7 @@ class ParseSchema {
    * Valid options are:<ul>
    *   <li>required: If field is not set, save operation fails (Requires Parse Server 3.7.0+)
    *   <li>defaultValue: If field is not set, a default value is selected (Requires Parse Server 3.7.0+)
+   *   <li>targetClass: Required if type is Pointer or Parse.Relation
    * </ul>
    * @returns {Parse.Schema} Returns the schema, so you can chain this call.
    */
@@ -226,6 +227,12 @@ class ParseSchema {
     if (FIELD_TYPES.indexOf(type) === -1) {
       throw new Error(`${type} is not a valid type.`);
     }
+    if (type === 'Pointer') {
+      return this.addPointer(name, options.targetClass, options);
+    }
+    if (type === 'Relation') {
+      return this.addRelation(name, options.targetClass, options);
+    }
     const fieldOptions = { type };
 
     if (typeof options.required === 'boolean') {


Test patch:
diff --git a/integration/test/ParseObjectTest.js b/integration/test/ParseObjectTest.js
index 65d9322..3c94ad3 100644
--- a/integration/test/ParseObjectTest.js
+++ b/integration/test/ParseObjectTest.js
@@ -2012,4 +2012,33 @@ describe('Parse Object', () => {
     const fetched = await query.get(user.id);
     assert.equal(fetched.isDataAvailable(), true);
   });
+
+  it('from json save data', async () => {
+    const json = {
+      className: 'TestObject',
+      date: new Date(),
+      array: [],
+      object: {},
+      string: '',
+    };
+    const obj = Parse.Object.fromJSON(json, false, true);
+    expect(obj.get('date')).toBeDefined();
+    expect(obj.get('date')).toBeInstanceOf(Date);
+    expect(obj.get('array')).toBeDefined();
+    expect(obj.get('array')).toBeInstanceOf(Array);
+    expect(obj.get('object')).toBeDefined();
+    expect(obj.get('object')).toBeInstanceOf(Object);
+    expect(obj.get('string')).toBeDefined();
+    expect(obj.get('string')).toBeInstanceOf(String);
+    await obj.save();
+    await obj.fetch();
+    expect(obj.get('date')).toBeDefined();
+    expect(obj.get('date')).toBeInstanceOf(Date);
+    expect(obj.get('array')).toBeDefined();
+    expect(obj.get('array')).toBeInstanceOf(Array);
+    expect(obj.get('object')).toBeDefined();
+    expect(obj.get('object')).toBeInstanceOf(Object);
+    expect(obj.get('string')).toBeDefined();
+    expect(obj.get('string')).toBeInstanceOf(String);
+  });
 });
diff --git a/integration/test/ParseQueryTest.js b/integration/test/ParseQueryTest.js
index c146fd2..4815585 100644
--- a/integration/test/ParseQueryTest.js
+++ b/integration/test/ParseQueryTest.js
@@ -48,6 +48,30 @@ describe('Parse Query', () => {
       .catch(done.fail);
   });
 
+  it('can return raw json from queries', async () => {
+    const object = new TestObject({ foo: 'bar' });
+    await object.save();
+
+    const query = new Parse.Query(TestObject);
+    const results = await query.find({ json: true });
+    assert.strictEqual(results[0] instanceof Parse.Object, false);
+    assert.strictEqual(results[0].foo, 'bar');
+    assert.strictEqual(results[0].className, 'TestObject');
+    assert.strictEqual(results[0].objectId, object.id);
+
+    let result = await query.first({ json: true });
+    assert.strictEqual(result instanceof Parse.Object, false);
+    assert.strictEqual(result.foo, 'bar');
+    assert.strictEqual(result.className, 'TestObject');
+    assert.strictEqual(result.objectId, object.id);
+
+    result = await query.get(object.id, { json: true });
+    assert.strictEqual(result instanceof Parse.Object, false);
+    assert.strictEqual(result.foo, 'bar');
+    assert.strictEqual(result.className, 'TestObject');
+    assert.strictEqual(result.objectId, object.id);
+  });
+
   it('can do query with count', async () => {
     const items = [];
     for (let i = 0; i < 4; i++) {
diff --git a/src/__tests__/ParseObject-test.js b/src/__tests__/ParseObject-test.js
index 7578c74..2dbdf35 100644
--- a/src/__tests__/ParseObject-test.js
+++ b/src/__tests__/ParseObject-test.js
@@ -262,7 +262,7 @@ describe('ParseObject', () => {
       createdAt: '2013-12-14T04:51:19Z',
       objectId: 'I1',
       size: 'medium',
-      date: date
+      date: date,
     };
     const o = ParseObject.fromJSON(json);
     expect(o.className).toBe('Item');
@@ -271,12 +271,34 @@ describe('ParseObject', () => {
       size: 'medium',
       createdAt: new Date(Date.UTC(2013, 11, 14, 4, 51, 19)),
       updatedAt: new Date(Date.UTC(2013, 11, 14, 4, 51, 19)),
-      date
+      date,
     });
     expect(o.dirty()).toBe(false);
     expect(o.get('date')).toBeInstanceOf(Date);
   });
 
+  it('can be dirty with fromJSON', () => {
+    const date = new Date();
+    const json = {
+      className: 'Item',
+      createdAt: '2013-12-14T04:51:19Z',
+      objectId: 'I1',
+      size: 'medium',
+      date: date,
+    };
+    const o = ParseObject.fromJSON(json, false, true);
+    expect(o.className).toBe('Item');
+    expect(o.id).toBe('I1');
+    expect(o.attributes).toEqual({
+      size: 'medium',
+      createdAt: new Date(Date.UTC(2013, 11, 14, 4, 51, 19)),
+      updatedAt: new Date(Date.UTC(2013, 11, 14, 4, 51, 19)),
+      date,
+    });
+    expect(o.dirty()).toBe(true);
+    expect(o.dirtyKeys()).toEqual(['size', 'date']);
+  });
+
   it('can override old data when inflating from the server', () => {
     const o = ParseObject.fromJSON({
       className: 'Item',
diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js
index 5aea441..26c555b 100644
--- a/src/__tests__/ParseQuery-test.js
+++ b/src/__tests__/ParseQuery-test.js
@@ -1341,6 +1341,43 @@ describe('ParseQuery', () => {
     });
   });
 
+  it('can return raw json from query', async () => {
+    CoreManager.setQueryController({
+      aggregate() {},
+      find() {
+        return Promise.resolve({
+          results: [
+            {
+              objectId: 'I1',
+              size: 'small',
+              name: 'Product 3',
+            },
+          ],
+        });
+      },
+    });
+
+    const q = new ParseQuery('Item');
+    q.equalTo('size', 'small');
+    const results = await q.find({ json: true });
+    expect(results[0].objectId).toBe('I1');
+    expect(results[0].size).toBe('small');
+    expect(results[0].name).toEqual('Product 3');
+    expect(results[0].className).toEqual('Item');
+
+    let result = await q.first({ json: true });
+    expect(result.objectId).toBe('I1');
+    expect(result.size).toBe('small');
+    expect(result.name).toEqual('Product 3');
+    expect(result.className).toEqual('Item');
+
+    result = await q.get(result.objectId, { json: true });
+    expect(result.objectId).toBe('I1');
+    expect(result.size).toBe('small');
+    expect(result.name).toEqual('Product 3');
+    expect(result.className).toEqual('Item');
+  });
+
   it('will error when getting a nonexistent object', done => {
     CoreManager.setQueryController({
       aggregate() {},
@@ -3260,7 +3297,12 @@ describe('ParseQuery LocalDatastore', () => {
       updatedAt: new Date('2018-08-12T00:00:00.000Z'),
     };
 
-    mockLocalDatastore._serializeObjectsFromPinName.mockImplementation(() => [obj1, obj3, obj2, obj4]);
+    mockLocalDatastore._serializeObjectsFromPinName.mockImplementation(() => [
+      obj1,
+      obj3,
+      obj2,
+      obj4,
+    ]);
 
     mockLocalDatastore.checkIfEnabled.mockImplementation(() => true);
 
diff --git a/src/__tests__/ParseSchema-test.js b/src/__tests__/ParseSchema-test.js
index 92386ee..983c1a2 100644
--- a/src/__tests__/ParseSchema-test.js
+++ b/src/__tests__/ParseSchema-test.js
@@ -92,6 +92,18 @@ describe('ParseSchema', () => {
     done();
   });
 
+  it('can create schema pointer and relation with addFields', () => {
+    const schema = new ParseSchema('SchemaTest');
+    schema
+      .addField('newPointer', 'Pointer', { targetClass: '_User' })
+      .addField('newRelation', 'Relation', { targetClass: '_User' });
+
+    expect(schema._fields.newPointer.type).toEqual('Pointer');
+    expect(schema._fields.newRelation.type).toEqual('Relation');
+    expect(schema._fields.newPointer.targetClass).toEqual('_User');
+    expect(schema._fields.newRelation.targetClass).toEqual('_User');
+  });
+
   it('can create schema fields required and default values', () => {
     const object = new ParseObject('TestObject', '1234');
     const schema = new ParseSchema('SchemaTest');