Skip to content
35 changes: 35 additions & 0 deletions spec/ParseObject.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,41 @@ describe('Parse.Object testing', () => {
);
});

it_only_db('mongo')('can increment array nested fields', async () => {
const obj = new TestObject();
obj.set('items', [ { value: 'a', count: 5 }, { value: 'b', count: 1 } ]);
await obj.save();
obj.increment('items.0.count', 15);
obj.increment('items.1.count', 4);
await obj.save();
expect(obj.toJSON().items[0].value).toBe('a');
expect(obj.toJSON().items[1].value).toBe('b');
expect(obj.toJSON().items[0].count).toBe(20);
expect(obj.toJSON().items[1].count).toBe(5);
const query = new Parse.Query(TestObject);
const result = await query.get(obj.id);
expect(result.get('items')[0].value).toBe('a');
expect(result.get('items')[1].value).toBe('b');
expect(result.get('items')[0].count).toBe(20);
expect(result.get('items')[1].count).toBe(5);
expect(result.get('items')).toEqual(obj.get('items'));
});

it_only_db('mongo')('can increment array nested fields missing index', async () => {
const obj = new TestObject();
obj.set('items', []);
await obj.save();
obj.increment('items.1.count', 15);
await obj.save();
expect(obj.toJSON().items[0]).toBe(null);
expect(obj.toJSON().items[1].count).toBe(15);
const query = new Parse.Query(TestObject);
const result = await query.get(obj.id);
expect(result.get('items')[0]).toBe(null);
expect(result.get('items')[1].count).toBe(15);
expect(result.get('items')).toEqual(obj.get('items'));
});

it('addUnique with object', function (done) {
const x1 = new Parse.Object('X');
x1.set('stuff', [1, { hello: 'world' }, { foo: 'bar' }]);
Expand Down
7 changes: 7 additions & 0 deletions src/Controllers/DatabaseController.js
Original file line number Diff line number Diff line change
Expand Up @@ -1851,6 +1851,13 @@ class DatabaseController {
// only valid ops that produce an actionable result
// the op may have happened on a keypath
this._expandResultOnKeyPath(response, key, result);
// Revert array to object conversion on dot notation for arrays (e.g. "field.0.key")
if (key.includes('.')) {
const [field, index] = key.split('.');
if (!isNaN(index) && Array.isArray(result[field]) && !Array.isArray(response[field])) {
response[field] = result[field];
}
}
}
});
return Promise.resolve(response);
Expand Down
14 changes: 11 additions & 3 deletions src/Controllers/SchemaController.js
Original file line number Diff line number Diff line change
Expand Up @@ -1096,9 +1096,17 @@ export default class SchemaController {
maintenance?: boolean
) {
if (fieldName.indexOf('.') > 0) {
// subdocument key (x.y) => ok if x is of type 'object'
fieldName = fieldName.split('.')[0];
type = 'Object';
// "<array>.<index>" for Nested Arrays
// "<embedded document>.<field>" for Nested Objects
// JSON Arrays are treated as Nested Objects
const [x, y] = fieldName.split('.');
fieldName = x;
const isArrayIndex = Array.from(y).every(c => c >= '0' && c <= '9');
if (isArrayIndex && !['sentPerUTCOffset', 'failedPerUTCOffset'].includes(fieldName)) {
type = 'Array';
} else {
type = 'Object';
}
}
let fieldNameToValidate = `${fieldName}`;
if (maintenance && fieldNameToValidate.charAt(0) === '_') {
Expand Down