Skip to content

Instantly share code, notes, and snippets.

@pascalgiguere
Created May 12, 2016 23:24
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save pascalgiguere/6978c87a2e9a63ddf7577ddaf1b32f3c to your computer and use it in GitHub Desktop.
Save pascalgiguere/6978c87a2e9a63ddf7577ddaf1b32f3c to your computer and use it in GitHub Desktop.
Node.js script using the Parse REST API to import a JSON file to Parse. Useful when dropping an entire class then reloading it from a JSON backup (Parse export). Will also update pointers of a second Parse class pointing to the imported class so that pointers don't break despite the imported data having different objectIds.
var PARSE_APPLICATION_ID = '';
var PARSE_REST_API_KEY = '';
var JSON_FILE_PATH = ''; // Path to JSON file to import
var IMPORTED_CLASS_NAME = ''; // Class to import
var POINTING_CLASS_NAME = ''; // Class with pointers to imported class
var POINTING_CLASS_PROPERTY = ''; // Name of pointer property
var request = require('request');
var fs = require('fs');
// Import objects from JSON to Parse
importFromJson(JSON_FILE_PATH, IMPORTED_CLASS_NAME, function(newObjectIds) {
console.log('Successfully imported objects from JSON.');
// Update objects from external Parse class pointing to imported objects
updatePointingObjects(IMPORTED_CLASS_NAME, POINTING_CLASS_NAME, POINTING_CLASS_PROPERTY, newObjectIds, function() {
console.log('Successfully updated pointing objects.');
});
});
function importFromJson(jsonFilePath, importedClassName, callback) {
// Store new objectIds associated to their original objectIds
// Will be used to update pointers of other Parse classes
var newObjectIds = {};
// Read and parse JSON file
var json = JSON.parse(fs.readFileSync(jsonFilePath, 'utf8'));
// Delay requests with setTimeout to stay under Parse's limit
delayedAsyncLoop(function(i, fnCallback) {
// Loop
var obj = json.results[i - 1];
var originalObjectId = obj.objectId;
// Create object using Parse REST API
createObject(importedClassName, obj, function(newObj) {
// Abort if request fails
if (!newObj) process.exit(-1);
// Associate the object's new objectId to its original objectId
newObjectIds[originalObjectId] = newObj.objectId;
fnCallback();
});
},
json.results.length, // Iterations
100, // Delay in milliseconds
function() { // Done looping
callback(newObjectIds);
});
}
function updatePointingObjects(importedClassName, pointingClassName, pointingClassProperty, newObjectIds, callback) {
// Get all objects from another Parse class that point to our imported class
getAllPointingObjects(pointingClassName, pointingClassProperty, function(pointingObjects) {
// Abort if request fails
if (!pointingObjects) process.exit(-1);
var nbObjectsToUpdate = pointingObjects.length;
// Delay requests with setTimeout to stay under Parse's limit
delayedAsyncLoop(function(i, fnCallback) {
// Loop
var pointingObject = pointingObjects[i - 1];
var pointer = pointingObject[pointingClassProperty];
if (!pointer || pointer.className != importedClassName) {
fnCallback();
nbObjectsToUpdate--;
if (!nbObjectsToUpdate) callback(); // Done updating pointing objects
return;
}
// Retrieve the new objectId each pointer should be updated with
var originalObjectId = pointer.objectId;
var newObjectId = newObjectIds[originalObjectId];
if (!newObjectId) {
fnCallback();
nbObjectsToUpdate--;
if (!nbObjectsToUpdate) callback(); // Done updating pointing objects
return;
}
// Update pointer to the new objectId
updatePointingObject(pointingClassName, pointingClassProperty, pointingObject.objectId, importedClassName, newObjectId, function() {
fnCallback();
nbObjectsToUpdate--;
if (!nbObjectsToUpdate) callback(); // Done updating pointing objects
});
},
pointingObjects.length, // Iterations
100 // Delay in milliseconds
);
});
}
function delayedAsyncLoop (fn, iterations, delay, callback) {
(function loop (i, done) {
setTimeout(function() {
fn(i, function() {
if (--i) {
// Keep looping
loop(i, done);
} else {
// Loop done
if (done) done();
}
});
}, delay)
})(iterations, callback);
}
function createObject(className, object, callback) {
delete object.objectId;
delete object.createdAt;
delete object.updatedAt;
request({
method: 'POST',
url: 'https://api.parse.com/1/classes/' + className,
headers: {
'X-Parse-Application-Id': PARSE_APPLICATION_ID,
'X-Parse-REST-API-Key': PARSE_REST_API_KEY,
'Content-Type': 'application/json; charset=UTF-8'
},
body: JSON.stringify(object)
}, function(error, response, body) {
if (response.statusCode == 201) {
var result = JSON.parse(body);
object.objectId = result.objectId;
object.createdAt = result.createdAt;
object.updatedAt = result.updatedAt;
console.log('Created ' + className + ' object with objectId ' + result.objectId);
callback(object);
} else {
console.log('Error: ' + response.statusCode);
console.log(body);
callback();
}
});
}
function getAllPointingObjects(className, pointingProperty, callback) {
getPointingObjectsRecursive([], className, pointingProperty, 0, null, callback)
}
function getPointingObjectsRecursive(allObjects, className, pointingProperty, skipNb, minCreatedAt, callback) {
var whereObj = {};
whereObj[pointingProperty] = {
'$exists': true
};
if (minCreatedAt) {
whereObj['createdAt'] = {
'$gt': minCreatedAt
};
}
var queryString = {
'limit': 1000,
'order': 'createdAt',
'skip': skipNb,
'where': JSON.stringify(whereObj)
};
request({
method: 'GET',
url: 'https://api.parse.com/1/classes/' + className,
headers: {
'X-Parse-Application-Id': PARSE_APPLICATION_ID,
'X-Parse-REST-API-Key': PARSE_REST_API_KEY
},
qs: queryString
}, function(error, response, body) {
if (response.statusCode == 200) {
var results = JSON.parse(body).results;
Array.prototype.push.apply(allObjects, results);
if (results.length == 1000) {
// Keep fetching
if (skipNb > 10000) {
minCreatedAt = results[999].createdAt;
skipNb = 0;
}
getPointingObjectsRecursive(allObjects, className, pointingProperty, skipNb+1000, minCreatedAt, callback);
} else {
// All objects fetched
callback(allObjects);
}
} else {
console.log('Error: ' + response.statusCode);
console.log(body);
callback();
}
});
}
function updatePointingObject(pointingClassName, pointingClassProperty, pointingObjectId, pointedClassName, pointedObjectId, callback) {
var pointer = {
"__type": "Pointer",
"className": pointedClassName,
"objectId": pointedObjectId
};
var requestBody = {};
requestBody[pointingClassProperty] = pointer;
request({
method: 'PUT',
url: 'https://api.parse.com/1/classes/' + pointingClassName + '/' + pointingObjectId,
headers: {
'X-Parse-Application-Id': PARSE_APPLICATION_ID,
'X-Parse-REST-API-Key': PARSE_REST_API_KEY,
'Content-Type': 'application/json; charset=UTF-8'
},
body: JSON.stringify(requestBody)
}, function(error, response, body) {
if (response.statusCode == 200) {
console.log('Updated pointer of ' + pointingClassName + '/' + pointingObjectId + ' to ' + pointedObjectId);
callback();
} else {
console.log('Error: ' + response.statusCode);
console.log(body);
callback();
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment