How to use boost::property_tree to load and write JSON

Property Tree is a sublibrary of boost that allows you to handle trees of properties. It can be used to represent XML, JSON, INI files, file paths, etc. In our case, we will be interested in loading and writing JSON, to provide an interface with other applications.

Our example case will be the following json file :

{
    "height" : 320,
    "some" :
    {
        "complex" :
        {
            "path" : "hello"
        }
    },
    "animals" :
    {
        "rabbit" : "white",
        "dog" : "brown",
        "cat" : "grey"
    },
    "fruits" : ["apple", "raspberry", "orange"],
    "matrix" : [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
}

Reading data

Let’s have a look at how we can load those data into our c++ application.

Setting up

First, we need to include the libraries and load the file.

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>

// Short alias for this namespace
namespace pt = boost::property_tree;

// Create a root
pt::ptree root;

// Load the json file in this ptree
pt::read_json("filename.json", root);

Now, we have a populated property tree that is waiting for us to look at it. Notice that you can also read from a stream, for example pt::read_json(std::cin, root){.cpp} is also allowed.

If your json file is ill-formed, you will be greeted by a pt::json_parser::json_parser_error.

Loading some values

We can access a value from the root by giving its path to the get method.

// Read values
int height = root.get<int>("height", 0);
// You can also go through nested nodes
std::string msg = root.get<std::string>("some.complex.path");

If the field you are looking for doesn’t exist, the get() method will throw a pt::ptree_bad_path exception, so that you can recover from incomplete json files. Notice you can set a default value as second argument, or use get_optional<T>() which returns a boost::optional<T>.

Notice the getter doesn’t care about the type of the input in the json file, but only relies on the ability to convert the string to the type you are asking for.

Browsing lists

So now, we would like to read a list of objects (in our cases, a list of animals).

We can handle it with a simple for loop, using an iterator. In c++11, it becomes :

// A vector to allow storing our animals
std::vector< std::pair<std::string, std::string> > animals;

// Iterator over all animals
for (pt::ptree::value_type &animal : root.get_child("animals"))
{
    // Animal is a std::pair of a string and a child

    // Get the label of the node
    std::string name = animal.first;
    // Get the content of the node
    std::string color = animal.second.data();
    animals.push_back(std::make_pair(name, color));
}

Since animal.second is a ptree, we can also call get() or get_child() in the case our node wasn’t just a string.

A bit more complex example is given by a list of values. Each element of the list is actually a std::pair("", value) (where value is a ptree). It doesn’t mean that reading it is harder.

std::vector<std::string> fruits;
for (pt::ptree::value_type &fruit : root.get_child("fruits"))
{
    // fruit.first contain the string ""
    fruits.push_back(fruit.second.data());
}

In the case the values aren’t strings, we can just call fruit.second.get_value<T>() in place of fruit.second.data().

Deeper : matrices

There is nothing new to enable reading of matrices, but it’s a good way to check that you understood the reading of lists. But enough talking, let’s have a look at the code.

int matrix[3][3];
int x = 0;
for (pt::ptree::value_type &row : root.get_child("matrix"))
{
    int y = 0;
    for (pt::ptree::value_type &cell : row.second)
    {
        matrix[x][y] = cell.second.get_value<int>();
        y++;
    }
    x++;
}

You can now read any kind of JSON tree. The next step is being able to read them.

Writing JSON

Let’s say that now, we want to produce this tree from our application’s data. To do that, all we have to do is build a ptree containing our data.

We start with an empty tree :

pt::ptree root;

//...

// Once our ptree was constructed, we can generate JSON on standard output
pt::write_json(std::cout, root);

Add values

Putting values in a tree can be accomplished with the put() method.

root.put("height", height);
root.put("some.complex.path", "bonjour");

As you can see, very boring.

Add a list of objects

No big deal here, although we now use add_child() to put our animal node at the root.

// Create a node
pt::ptree animals_node;
// Add animals as childs
for (auto &animal : animals)
    animals_node.put(animal.first, animal.second);
// Add the new node to the root
root.add_child("animals", animals_node);

Add many nodes with the same name

Now start the tricky tricks. If you want to add more than one time a node named fish, you can’t call the put() method. The call node.put("name", value) will replace the existing node named name. But you can do it by manually pushing your nodes, as demonstrated below.

 // Add two objects with the same name
 pt::ptree fish1;
 fish1.put_value("blue");
 pt::ptree fish2;
 fish2.put_value("yellow");
 oroot.push_back(std::make_pair("fish", fish1));
 oroot.push_back(std::make_pair("fish", fish2));

Add a list of values

If you remember, lists are made of nodes with empty names. So we have to build nodes with empty names, and then use the push_back() once again to add all those unnamed children.

// Add a list
pt::ptree fruits_node;
for (auto &fruit : fruits)
{
    // Create an unnamed node containing the value
    pt::ptree fruit_node;
    fruit_node.put("", fruit);

    // Add this node to the list.
    fruits_node.push_back(std::make_pair("", fruit_node));
}
root.add_child("fruits", fruits_node);

Add a matrix

We already have all the tools needed to export our matrix. But let’s demonstrate how to do it.

// Add a matrix
pt::ptree matrix_node;
for (int i = 0; i < 3; i++)
{
    pt::ptree row;
    for (int j = 0; j < 3; j++)
    {
        // Create an unnamed value
        pt::ptree cell;
        cell.put_value(matrix[i][j]);
        // Add the value to our row
        row.push_back(std::make_pair("", cell));
    }
    // Add the row to our matrix
    matrix_node.push_back(std::make_pair("", row));
}
// Add the node to the root
root.add_child("matrix", matrix_node);

References

You can download a C++ example and the input JSON file for experimenting. Compile it with clang++ -std=c++11 example.cpp -o example.

Some links related :

Rem : At the time of writing, the boost::property_tree library doesn’t output typed values. But we can expect that it will be corrected soon.