1

I have to read some data from a JSON file using C++. After some research I found Boost Property Tree as the most suggested approach and I was able to get it up and running with simple strings. However, when it comes to deep, nested arrays and matrices, I have found no solution that is working for me.

This is what my JSON file looks like. There can be multiple "objects" with their data each:

{
    "some_data":
    {
        (...)
    },
    "objects": [
        {
            "name": "Some name",
            "id": 0,
            "array": [ 1.9352999925613403, -1.0619000196456909, 38.685501098632813 ],
            "matrix": [
                [ -0.74119997024536133, -0.56959998607635498, 0.35519999265670776, 0 ],
                [ 0.61210000514984131, -0.35649999976158142, 0.70579999685287476, 0 ],
                [ 0.27540001273155212, -0.74059998989105225, -0.6129000186920166, 0 ],
                [ 1.9352999925613403, -1.0619000196456909, 38.685501098632813, 1 ]
            ],
            (...)
        },
        (...)
    ]
}

I use the following code to successfully retrieve data stored in the string "name" (both loops work just fine):

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

int main() {
    boost::property_tree::ptree root;
    boost::property_tree::read_json("dataFile.json", root);
    std::string name;

    BOOST_FOREACH(boost::property_tree::ptree::value_type& v, root.get_child("objects")) {
        name =  v.second.get<std::string>("name");
    }

    for(boost::property_tree::ptree::value_type& v : root.get_child("objects")) {
        name =  v.second.get<std::string>("name");
    }

    return 0;
}

Is there a similar approach or a little extension to my code that allows me to get the values of the array and matrix? I was not able to nest another for(each) loop inside the one iterating through "objects" but could not get the array or matrix data at once either.

Thank you for any help in advance!

codesieve
  • 13
  • 4
  • 2
    Consider using a library for this, such as [nlohmann json](https://github.com/nlohmann/json). It will make the job much easier. – Paul Sanders May 13 '20 at 13:57
  • As good as `boost` is, I think the property_tree is trying to be all things for all people, thus may not do everything a dedicated JSON parser is able to do. Better to get a JSON parsing library, such as the one suggested in the previous comment. – PaulMcKenzie May 13 '20 at 14:00
  • @PaulMcKenzie Boost Property Tree is trying to be nothing but Property Tree. They support persistance in various formats that are subsets of known formats (e.g. XML, JSON). That is widely abused for purposes other than Property Trees. And it "may not do" certainly needs to be "it doesn't do" - as very clearly documented eg [here](https://www.boost.org/doc/libs/1_73_0/doc/html/property_tree/parsers.html#property_tree.parsers.json_parser). Relevant: https://twitter.com/sehetw/status/1125128091611824135 – sehe May 13 '20 at 15:10
  • Thanks for your answers, I am going to work with libraries like the nlohmann one in the future. Really appreciate it! – codesieve May 14 '20 at 09:34

1 Answers1

0

Like I warn in my comment, Boost Property Tree is not a JSON library. This doesn't mean that what you ask for here is impossible.

It is possible. Let's pick Array and Matrix definitions:

using Array = std::array<double, 3>;
using Matrix = std::array<std::array<double, 4>, 4>;

My preferred strategy (instead of using the Translators framework from Property Tree), is to write procedural helpers:

void read(ptree const& pt, double& into);
void read(ptree const& pt, Array& into);
void read(ptree const& pt, Matrix& into);

In fact, we can catch both Array/Matrix in the same implementation:

void read(ptree const& pt, double& into) {
    into = pt.get_value<double>();
}

template <typename ArrayOrMatrix>
void read(ptree const& pt, ArrayOrMatrix& into) {
    auto elements = pt.equal_range(""); // unnamed elements

    assert(boost::size(elements) == into.size());
    auto out = into.begin();
    for (auto& [_,v] : boost::make_iterator_range(elements)) {
        read(v, *out++);
    }
}

Now we can simply write our program as:

boost::property_tree::ptree pt;
read_json(iss, pt);

for (auto& [_,obj] : pt.get_child("objects")) {
    std::cout << "Reading object " << std::quoted(obj.get<std::string>("name")) << "\n";
    Array a;
    Matrix m;

    read(obj.get_child("array"), a);
    read(obj.get_child("matrix"), m);
}

See it Live On Coliru

#include <boost/property_tree/json_parser.hpp>
#include <iostream>
#include <iomanip>
#include <cassert>

using boost::property_tree::ptree;

using Array = std::array<double, 3>;
using Matrix = std::array<std::array<double, 4>, 4>;

void read(ptree const& pt, double& into) {
    into = pt.get_value<double>();
}

template <typename ArrayOrMatrix>
void read(ptree const& pt, ArrayOrMatrix& into) {
    auto elements = pt.equal_range(""); // unnamed elements

    assert(boost::size(elements) == into.size());
    auto out = into.begin();
    for (auto& [_,v] : boost::make_iterator_range(elements)) {
        read(v, *out++);
    }
}

int main() {

    std::istringstream iss(R"({
    "some_data":
    {
    },
    "objects": [
        {
            "name": "Some name",
            "id": 0,
            "array": [ 1.9352999925613403, -1.0619000196456909, 38.685501098632813 ],
            "matrix": [
                [ -0.74119997024536133, -0.56959998607635498, 0.35519999265670776, 0 ],
                [ 0.61210000514984131, -0.35649999976158142, 0.70579999685287476, 0 ],
                [ 0.27540001273155212, -0.74059998989105225, -0.6129000186920166, 0 ],
                [ 1.9352999925613403, -1.0619000196456909, 38.685501098632813, 1 ]
            ],
            "more": {}
        }
    ]
})");

    boost::property_tree::ptree pt;
    read_json(iss, pt);

    for (auto& [_,obj] : pt.get_child("objects")) {
        std::cout << "Reading object " << std::quoted(obj.get<std::string>("name")) << "\n";
        Array a;
        Matrix m;

        read(obj.get_child("array"), a);
        read(obj.get_child("matrix"), m);
    }

    //write_json(std::cout, pt);
}

Prints:

Reading object "Some name"
sehe
  • 374,641
  • 47
  • 450
  • 633