Created
May 12, 2016 23:24
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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