How to do Partial Queries and Updates to a Complex JSON Document Using the Game Data Service

There are times where you want to maintain a complex JSON document in a Data Type that you want to use for storing state. This tutorial show you how to do partial queries and updates to a complex JSON document using the Game Data Service.

In this example:

A concrete example of this sort of case might be where you are storing custom data about every level the player has ever played:

    {
        "LEVEL_1": {
        "checkpoint": 5,
        "complete": 1
      },
        "LEVEL_2": {
        "checkpoint": 3,
        "complete": 0
      },
        "LEVEL_3": {
        "checkpoint": 0
      }
    }


More Information! For further details and recommendations on how to use Data Types to store and manage your game data, see the Managing Data Persistence tutorial.

Configuring the Game

Creating the Event for Updating the Document

We're creating a generic Event for updating a document. This Event will contain the path of the document we want to update and the value we want to set at that path. If this sounds a little complex, bear with us and work through the example - it should all become clear by the time you've work through the example!

We'll add a new Event in the portal that looks like this:

Creating the Script to Process the Update Event

We now need to bind a Cloud Code script to this Event to access the data and make the document updates. This script uses Game Data Service functionality to create the document if it doesn't already exist, and sets the value you want at the path you ask for:

//Get the value that was passed in for PATH
var inputPath = Spark.data.PATH;
//Get the value that was passed in for VAL
var inputValue = Spark.data.VAL;      

//Function to set value at end of path
function set(obj, path, value) {
    var schema = obj;  // a moving reference to internal objects within obj
    var pList = path.split('.'); //Split the path, creating an array entry for every string split by '.'
    var len = pList.length; //Get Length of list
    for(var i = 0; i < len-1; i++) { //For every index except the last one, reach the location in the data object
        var elem = pList[i];
        if( !schema[elem] ) schema[elem] = {}
        schema = schema[elem];
    }

    schema[pList[len-1]] = value; //Set the value of the final part of the path. This will update the object you passed in.
}

//Create entry and get its data object
var API = Spark.getGameDataService();
var playerId = Spark.getPlayer().getPlayerId();
//Get entry, data is best accessed via ID
var entryOBJ = API.getItem("playerProgress", playerId);
var data = {};
if(entryOBJ.error()){

} else{
    //If document exists
    if(entryOBJ.document()){  
        entry = entryOBJ.document();
    }
    else{
        entry = API.createItem("playerProgress", playerId);
    }
}

//Get the data object where custom data is stored
var data = entry.getData();

//Set value of variable at path
set(data, inputPath, inputValue);

//Persist and return any errors
var status = entry.persistor().persist().error();

//If there are errors the entry would not persist and we can act on that information
if(status){
    //Output error script
    Spark.setScriptError("ERROR", status);
    //Stop execution of script
    Spark.exit();
}

Creating the Event for Querying the Document

We'll use the same pattern here for querying the document. For the query Event only one attribute is required - the path of the document you want to retrieve.

We'll add a new Event in the portal that looks like this:

Creating the Script to Process the Query Event

This script finds and returns the value at end of the path if a path is given, otherwise it will return the whole data document back:

//Get the value that was passed in for PATH
var inputPath = Spark.data.PATH;

//Load API and get entry
var API = Spark.getGameDataService();

//Attempt to get entry
var entryObject = API.getItem("playerProgress", Spark.getPlayer().getPlayerId());

//If error attempting to retrieve entry
if(entryObject.error()){
    Spark.setScriptError("ERROR", entryObject.error())
    //Exit script
    Spark.exit();
} else{
    //Get entry
    var entry = entryObject.document();
    //Access Data
    var queryData = entry.getData();
}


if(inputPath.length > 0){
//No input path, we want the whole document
//If inputPath is valid, we return only the value of path
var pList = inputPath.split('.'); //Split the path, creating an array entry for every string split by '.'
var len = pList.length; //Get Length of list
for(var i = 0; i < len-1; i++) { //For every index except the last one, reach the location in the data object
    var elem = pList[i];
    //If element is undefined return an error message and terminate script
    if( !queryData[elem] ) {
        Spark.setScriptError("ERROR", "Path is not valid");
        Spark.exit();
    }

    //Continue following the string path and access the object's values
    queryData = queryData[elem];
}
queryData = queryData[pList[len-1]]; //Set the value of the final part of the path. This will update the object you passed in.
//If data is undefined return an error message and terminate script
if(queryData === undefined){
    Spark.setScriptError("ERROR", "Path is not valid");
    Spark.exit();
}
}

//Return data
Spark.setScriptData("data", queryData);

Execution of the Configuration

Register a User to Use for the Tests

    {
        "@class": ".RegistrationRequest",
        "userName": "gamesparks",
        "password": "gspwd",
        "displayName": "GameSparks Test",
        "requestId": "1392894961229"
    }

    {
        "@class": ".RegistrationResponse",
        "authToken": "...",
        "displayName": "GameSparks Test",
        "requestId": "1392894961229",
        "userId": "5305e40f1c26ac2a6576c389"
    }

Start Playing Level 1

We'll first check if the user has any data for level 1:

    {
        "@class": ".LogEventRequest",
        "eventKey": "PROGRESS_QUERY",
        "PATH": "LEVEL_1",
        "requestId": "1392895355079"
    }
    {
        "@class": ".LogEventResponse",
        "requestId": "1392895355079",
        "scriptData": {
            "data": []
        }
    }

We can see now that the user has no data for level 1.

Reach a Checkpoint on Level 1

We want to update the document to put the checkpoint reached into the document:

    {
        "@class": ".LogEventRequest",
        "eventKey": "PROG_UPDATE",
        "PATH": "LEVEL_1.checkpoint",
        "VAL": 1,
        "requestId": "1392895516999"
    }
    {
        "@class": ".LogEventResponse",
        "requestId": "1392895516999"
    }

Once this request has been passed in, we can query the document to get the progress in the future if required:

    {
        "@class": ".LogEventRequest",
        "eventKey": "PROGRESS_QUERY",
        "PATH": "LEVEL_1",
        "requestId": "1392895355079"
    }
    {
        "@class": ".LogEventResponse",
        "requestId": "1392895614721",
        "scriptData": {
            "data": [
                {
                    "LEVEL_1": {
                        "checkpoint": 1
                    }
                }
             ]
    }
    }

Completing Level 1

We'll assume the player has already reached every checkpoint, now we just need to update the "complete" attribute in the document:

    {
      "@class": ".LogEventRequest",
      "eventKey": "PROG_UPDATE",
      "PATH": "LEVEL_1.complete",
      "VAL": 1,
      "requestId": "1392895719046"
    }
    {
      "@class": ".LogEventResponse",
      "requestId": "1392895719046"
    }

Again, we can now query the level 1 data for the user at any point and get the full details:

    {
      "@class": ".LogEventRequest",
      "eventKey": "PROGRESS_QUERY",
      "PATH": "LEVEL_1",
      "requestId": "1392895355079"
    }

Did this page help you? Please enter your feedback below. For questions about using this part of the platform, please contact support here