Working with JSON

The Cascades framework lets you store and manage data in JavaScript Object Notation (JSON) format. This data format is based on a subset of JavaScript, and is a common format for representing hierarchical data. It's easy to write and easy to parse, which makes it a good choice for storing simple data in your apps.

JSON is based on two fundamental ideas:

  • A collection of name/value pairs (an object)
  • An ordered list of values (an array)

An object is defined using braces ({ and }) and represents a list of names and their corresponding values. Names in an object need to be strings, but you can use several different data types as values, such as strings, numbers, or Boolean values (true and false). The name and value are separated by a colon (:), and each name-value pair in an object is separated by a comma (,).

An array is defined using square brackets ([ and ]) and represents a list of values. Each value can be a simple type, such as a string or a number, or a complex type, such as an object or another array. Each value in an array is separated by a comma.

Here's how you might represent a list of three employees using JSON data. Each employee is an object that contains the names firstNamelastName, and employeeNumber, with the associated values for each. The list itself is an array that contains the three objects.

[
    {
        "firstName" : "Mike",
        "lastName" : "Chepesky",
        "employeeNumber" : 01840192
    },
    {
        "firstName" : "Westlee",
        "lastName" : "Barichak",
        "employeeNumber" : 47901927
    },
    {
        "firstName" : "Ian",
        "lastName" : "Dundas",
        "employeeNumber" : 29472012
    }
]

For more information about JSON, see www.json.org.

You can use the  JsonDataAccess class to convert JSON data into Qt C++ objects. Then, you can add these Qt objects to a data model and display them in your app using a list view. The JsonDataAccess class is available only in C++ (not QML), and manages the connection to a source of JSON data, such as a file or buffer. The root element of the source JSON data needs to be either an array or an object. The JSON data that's presented above uses an array as the root element, so it's a valid source to use with JsonDataAccess.

Each JSON element type is mapped to a corresponding Qt value type, wrapped in a QVariant. For example, the real type in JSON is mapped to a QVariant(double) in Qt, the string type is mapped to a QVariant(const char *), and so on. For a full list of these mappings, see the API reference for the JsonDataAccess class.

There are two JSON element types that are particularly applicable when you're using JsonDataAccess. An array in JSON is mapped to a QVariantList, and an object is mapped to a QVariantMap. Because the root element of your JSON data must be either an array or an object, the corresponding Qt value type will be a QVariantList or a QVariantMap (wrapped in a QVariant). Data models in Cascades support both QVariantList and QVariantMap, which makes it easy to add data that's retrieved using JsonDataAccess directly to a data model. You'll see some examples of this approach in the next section.

Loading JSON data

The JsonDataAccess class and other supporting classes are included in the bb::data library. To use these classes in your apps, you need to add the following line to the .pro file in your project:

LIBS += -lbbdata

When you're ready to load JSON data into your app and start working with it, you create a JsonDataAccess object and call  load(), specifying the source of the data. The load() function returns a QVariant that wraps the root element of the JSON data (a QVariantList if the root is an array, or a QVariantMap if the root is an object). Then, you can add the data to a data model with an associated list view to display the data.

For example, consider the sample JSON data that's presented in the "Working with JSON data" section above. If this data is included in a file called employees.json (and this file is located in your project's assets folder), here's how to load the data and display it using a  GroupDataModel and  ListView. The GroupDataModel sorts the data by first name and last name. To learn more about GroupDataModel, see Sorted data models.

// Create the root page and list view
Page *root = new Page;
ListView *listView = new ListView;
 
// Create the data model, specifying sorting keys of "firstName" and "lastName"
GroupDataModel *model = new GroupDataModel(QStringList() << "firstName"
                                           << "lastName");
 
// Create a JsonDataAccess object and load the .json file. The 
// QDir::currentPath() function returns the current working 
// directory for the app.
JsonDataAccess jda;
QVariant list = jda.load(QDir::currentPath() +
                         "/app/native/assets/employees.json");
 
// Insert the data into the data model. Because the root of the .json file 
// is an array, a QVariant(QVariantList) is returned from load(). You can 
// provide a QVariantList to a data model directly by using insertList().
model->insertList(list.value<QVariantList>());
 
// Set the data model for the list view
listView->setDataModel(model);
 
// Set the content of the page and display it
root->setContent(listView);
app->setScene(root);

You might notice that the file path specified in load() is constructed using the QDir::currentPath() function. This function returns the current working directory for an app, which is the application's sandbox directory. By adding a relative file path, you can specify the location of the .json file that you want to load. The path "/app/native/assets/" specifies the location of the assets folder in your project, which is where the employees.json file is located. To learn more about the directories that your app can access, see Working with the file system.

Saving JSON data

In addition to loading data from an existing file, you can also create new files and add JSON data to them. To do so, you create a  JsonDataAccess object and then call  save(), specifying the data that you want to save and the file to save it to.

The data should be a QVariant that wraps the Qt objects that represent your data. For example, if your data is a list of cities, you should pass a QVariant that wraps a QVariantList to save(). Each item in the QVariantList would represent a single city, and you could use a structure such as a QVariantMap to encapsulate the properties of each city.

Here's how to save a list of employees to a temporary .json file. Each employee is represented by a QVariantMap, and these objects are added to a QVariantList and written to the .json file.

// Create QVariantMap objects to contain the data for each employee
QVariantMap firstEmployee, secondEmployee, thirdEmployee;
 
// Populate each object with data
firstEmployee["firstName"] = "Mike";
firstEmployee["lastName"] = "Chepesky";
firstEmployee["employeeNumber"] = 51649875;
secondEmployee["firstName"] = "Westlee";
secondEmployee["lastName"] = "Barichak";
secondEmployee["employeeNumber"] = 63257801;
thirdEmployee["firstName"] = "Ian";
thirdEmployee["lastName"] = "Dundas";
thirdEmployee["employeeNumber"] = 25669315;
 
// Add each employee to a QVariantList, and then wrap it in a QVariant
QVariant myData = QVariant(QVariantList() << QVariant(firstEmployee)
                           << QVariant(secondEmployee)
                           << QVariant(thirdEmployee));
 
// Retrieve the working directory, and create a temporary .json file in that
// location
QDir home = QDir::home();
QTemporaryFile file(home.absoluteFilePath("myJSONFile.json"));
 
// Open the file that was created
if (file.open()) {
    // Create a JsonDataAccess object and save the data to the file
    JsonDataAccess jda;
    jda.save(myData, &file);
}

Handling errors

When you're working with data files, you always hope that operations such as opening a file, reading data from it, and saving data to it are successful. However, sometimes these operations aren't successful and might generate errors. It's important that your apps handle these errors to provide the best user experience.

Fortunately,  JsonDataAccess includes a convenient way to detect whether a call to  load() or  save()generated an error, and there are several supporting classes that you can use to identify and process each type of error. Here's a summary of how you can detect data access errors:

  1. Call  JsonDataAccess::hasError() to determine whether the most recent operation produced an error.
  2. Call  JsonDataAccess::error() to retrieve the error that occurred. This function returns a DataAccessError object that contains information about the error, such as the type of error and a more detailed description.
  3. Compare the DataAccessError that you received from error() to enumeration values in the  DataAccessErrorType class (such as SourceNotFound and ConnectionError) to figure out the type of error that occurred.
  4. Handle each type of error as appropriate for your app.

Here's how to add error handling to the code sample that was presented in the Loading JSON data section. After the JSON data file is loaded, any errors that occur are processed and the text of a Label is updated appropriately.

// Create the root page, top-level container, list view, and error label
Page *root = new Page;
ListView *listView = new ListView;
Container *topContainer = new Container;
Label *errorLabel = new Label;
 
// Create the data model, specifying sorting keys of "firstName" and "lastName"
GroupDataModel *model = new GroupDataModel(QStringList() << "firstName"
                                           << "lastName");
 
// Create a JsonDataAccess object and load the .json file. The 
// QDir::currentPath() function returns the current working directory 
// for the app.
JsonDataAccess jda;
QVariant list = jda.load(QDir::currentPath() +
                         "/app/native/assets/employees.json");
 
// Determine if an error occurred during the load() operation
if (jda.hasError()) {
    // Retrieve the error
    DataAccessError theError = jda.error();
 
    // Determine the type of error that occurred
    if (theError.errorType() == DataAccessErrorType::SourceNotFound)
        errorLabel->setText("Source not found: " + theError.errorMessage());
    else if (theError.errorType() == DataAccessErrorType::ConnectionFailure)
        errorLabel->setText("Connection failure: " + theError.errorMessage());
    else if (theError.errorType() == DataAccessErrorType::OperationFailure)
        errorLabel->setText("Operation failure: " + theError.errorMessage());
} else {
    errorLabel->setText("No error.");
}
 
// Insert the data into the data model. Because the root of the .json file 
// is an array, a QVariant(QVariantList) is returned from load(). You can 
// provide a QVariantList to a data model directly by using insertList().
model->insertList(list.value<QVariantList>());
 
// Set the data model for the list view
listView->setDataModel(model);
 
// Set the content of the page and display it
root->setContent(listView);
app->setScene(root);

Last modified: 2013-12-21

comments powered by Disqus