Grzegorz
Grzegorz

Reputation: 3335

boost::property_tree::ptree accessing array's first complex element

My JSON is this:

{
    "apps":[
        {
            "id":"x",
            "val":"y",
        }
    ]
}

I can get id's value "x" by looping, and exiting when it.first is id:

for (const ptree::value_type &app : root.get_child("apps"))
{
    for (const ptree::value_type &it : app.second) {
        if (it.first == "id") {
            std::cout << it.second.get_value<std::string>().c_str() << std::endl;
        }
    }
}

What I want, however, is to get the id's value by something like this:

std::cout << root.get<std::string>("apps[0].id").c_str() << std::endl;

Of course this displays nothing to me, for I am probably using wrong syntax accessing 1st element of the apps array. It might be that this has to be done in a different way all together.

I have found only this ugly and dangerous method:

std::cout << root.get_child("apps").begin()->second.begin()->second.get_value<std::string>().c_str() << std::endl;

I cannot really use it this way as it won't throw an exception when the array is empty, it will core dump!

Below is the whole program to make it easier for any one who wants to help:

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

using boost::property_tree::ptree;

int main()
{
    std::stringstream ss("{\"apps\":[{\"id\":\"x\",\"val\":\"y\"}]}");

    ptree root;
    read_json(ss, root);

    for (const ptree::value_type &app : root.get_child("apps"))
    {
        for (const ptree::value_type &it : app.second) {
            if (it.first == "id") {
                std::cout << it.second.get_value<std::string>().c_str() << std::endl;
            }
        }
    }

    std::cout << root.get_child("apps").begin()->second.begin()->second.get_value<std::string>().c_str() << std::endl;

    return 0;
}

Upvotes: 3

Views: 3732

Answers (1)

sehe
sehe

Reputation: 393174

As the docs say, array elements are nodes with "" keys.

If you're after the first element, you're in luck:

root.get("apps..id", "")

The .. in the path selects the first empty key

Live On Coliru

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

using boost::property_tree::ptree;

int main() {
    std::stringstream ss(R"({"apps":[{"id":"x","val":"y"}]})");

    ptree root;
    read_json(ss, root);

    std::cout << root.get("apps..id", "") << "\n";
}

BONUS

If you need to address elements other than the first, write a helper function. This would be a good start:

#include <string> 
#include <stdexcept> // std::out_of_range

template <typename Tree>
Tree query(Tree& pt, typename Tree::path_type path) {
    if (path.empty())
        return pt;

    auto const head = path.reduce();

    auto subscript = head.find('[');
    auto name      = head.substr(0, subscript);
    auto index     = std::string::npos != subscript && head.back() == ']'
        ? std::stoul(head.substr(subscript+1))
        : 0u;

    auto matches = pt.equal_range(name);
    if (matches.first==matches.second)
        throw std::out_of_range("name:" + name);

    for (; matches.first != matches.second && index; --index)
        ++matches.first;

    if (index || matches.first==matches.second)
        throw std::out_of_range("index:" + head);

    return query(matches.first->second, path);
}

Here's some live tests using it:

Live On Coliru

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

using boost::property_tree::ptree;

int main() {
    std::stringstream ss(R"({
        "apps": [
            {
                "id": "x",
                "val": "y",
                "id": "hidden duplicate"
            },
            {
                "id": "a",
                "val": "b"
            }
        ]
    })");

    ptree root;
    read_json(ss, root);

    for (auto path : { 
        "apps..id",  "apps.[0].id", // (equivalent)
        //
        "apps.[0].id[]",  // invalid
        "apps.[0].id[0]", // x
        "apps.[0].id[1]", // hidden duplicate
        "apps.[1].id",    // a
        "apps.[1].id[0]", // a
        "apps.[1].id[1]", // out of range
        "apps.[2].id",    // out of range
        "drinks"          // huh, no drinks at the foo bar
    }) try {
        std::cout << "Path '" << path << "' -> ";
        std::cout << query(root, path).get_value<std::string>() << "\n";
    } catch(std::exception const& e) {
        std::cout << "Error: " << e.what() << "\n";
    }
}

Prints:

Path 'apps..id' -> x
Path 'apps.[0].id' -> x
Path 'apps.[0].id[]' -> Error: stoul
Path 'apps.[0].id[0]' -> x
Path 'apps.[0].id[1]' -> hidden duplicate
Path 'apps.[1].id' -> a
Path 'apps.[1].id[0]' -> a
Path 'apps.[1].id[1]' -> Error: index:id[1]
Path 'apps.[2].id' -> Error: index:[2]
Path 'drinks' -> Error: name:drinks

Upvotes: 4

Related Questions