Search code examples
c++jsonboostboost-propertytree

How can I read an array of object from a JSON file using boost-property-tree in C++


I am using boost-property-tree in a C++ application, where I am trying to read a JSON file users.json and store the data into a vector of object (std::vector<User> users;).

The JSON file looks this this:

{
   "OperatingSystem":"Windows 10",
   "users" :
   [
       {
          "firstName":"John",
          "lastName":"Black"
       },
       {
          "firstName":"Kate",
          "lastName":"Red"
       },
       {
          "firstName":"Robin",
          "lastName":"White"
       }
    ]
}

I have succedded reading the OperatingSystem property with the following line of code:

boost::property_tree::ptree treeRoot;

boost::property_tree::read_json("users.json", treeRoot);

std::string operatingSystem = treeRoot.get<std::string>("OperatingSystem");

std::cout << "OS : " << operatingSystem << std::endl;

and that works fine.

In order to store the users, I have created a User class. You can see the header file User.hpp below:

#ifndef USER_H
#define USER_H

#include <iostream>
#include <string>

class User
{
private:
    // Properties
    std::string firstName;
    std::string lastName;

public:
    // Constructors
    User();
    User(std::string firstName, std::string lastName);

    // Getters
    std::string getFirstName();
    std::string getLastName();

    // Setters
    void getFirstName(std::string firstName);
    void getLastName(std::string lastName);
};

#endif // USER_H

and the User.cpp file here:

#include "User.hpp"
#include <iostream>
#include <string>

// Constructors
User::User()
{
    this->firstName = "";
    this->lastName = "";
}

User::User(std::string firstName,   std::string lastName)
{
    this->firstName = firstName;
    this->lastName = lastName;
}

// Getters
std::string User::getFirstName()
{
    return firstName;
}

std::string User::getLastName()
{
    return lastName;
}

// Setters
void User::getFirstName(std::string firstName)
{
    this->firstName = firstName;
}

void User::getLastName(std::string lastName)
{
    this->lastName = lastName;
}

In my main.cpp I am trying to load the users into a vector as such:

#include <iostream>
#include <string>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include "User.hpp"

int main(int argc, char **argv)
{

    boost::property_tree::ptree treeRoot;

    boost::property_tree::read_json("users.json", treeRoot);

    std::vector<User> users;

    users = treeRoot.get<std::vector<User>>("users");

    return 0;
}

but it does not work.

In case someone knows how to read an array of objects from a JSON file via boost::property_tree::ptree, please let me know.


Solution

  • In property_tree arrays JSON are mapped into nodes (called just ptree). What is node/ptree ?

    node/ptree {
      data                              // has data
      list < pair<key,node> > children  // has children
    }
    

    In your input object you have property users with value as the array with 3 elements. These elements are mapped into three nodes with empty string as key.

    So we have:

    "users" node:
        children {"",nodeA}, {"",nodeB}, {"",nodeC}
    

    nodeA,B,C represents elements of arrays. Each element of array is object with 2 properties. Objects like arrays are also mapped into nodes.

    So nodeA looks like:

    nodeA:
        children {"firstName",nodeD},{"lastName",nodeE} \\ as children we have 2 properties of object
    

    and finally, nodeD is

    nodeD:
        data = John 
        children   emptyList
    

    To get users property call get_child().

    To iterate over all children use begin\end methods on ptree returned by get. begin\end returns iterator to pair with first as key, and second as nested ptree instance.

    The below code iterates over elements in array:

        boost::property_tree::ptree pt;
        boost::property_tree::read_json("in.json",pt);
    
        auto it = pt.get_child("users");
    
        for (auto it2 = it.begin(); it2 != it.end(); ++it2)
        {
            for (auto it3 = it2->second.begin(); it3 != it2->second.end(); ++it3)
            {
                std::cout  << it3->first; // [1]
                std::cout << " : " << it3->second.data() << std::endl;
            }
            // [2]
            std::cout << std::endl;
        }
    

    and prints:

    firstName : John
    lastName : Black
    
    firstName : Kate
    lastName : Red
    
    firstName : Robin
    lastName : White
    

    in [1] line you should store firstName/lastName and in [2] line you could create new User instance and push into vector.