3

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;
}
Grzegorz
  • 3,207
  • 3
  • 20
  • 43

1 Answers1

4

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
sehe
  • 374,641
  • 47
  • 450
  • 633
  • You are the man! Thank you so much! I am marking your answer as the one, and upping. Do you happen to know how to access nth element? – Grzegorz Jan 23 '18 at 23:43
  • WAIT A MINUTE - What is this 'R' macro? That's awesome! I see no definition of it, so it must be coming from one of the standard libraries or boost. Can you point me out to this? - OKAY I have found it as an element of the language. I have learned two more exciting things today :) Thank you! – Grzegorz Jan 23 '18 at 23:46
  • It's a [c++11 raw string literal](http://en.cppreference.com/w/cpp/language/string_literal) – sehe Jan 23 '18 at 23:49
  • Accessing the nth element requires you to write a helper function (e.g. [this one](https://stackoverflow.com/questions/45753571/access-to-specific-index-in-ptree-array/45758833#45758833)). Also interesting to [think outside](https://stackoverflow.com/questions/33459076/how-can-i-parse-json-arrays-with-c/33467322#33467322) the Property Tree library (because Boost [doesn't](https://stackoverflow.com/questions/30663260/how-array-parser/30663722#30663722) have [a JSON library](https://stackoverflow.com/questions/40292925/boost-ptree-top-level-array/40303231?s=9|30.5216#40303231)) – sehe Jan 23 '18 at 23:58
  • 1
    Yes. That's what I was warned about. There is [library](https://github.com/nlohmann/json/releases) that they say is better, but it would require me to go through legal department to get it. So I decided to use boost, as we are already using it, and I need to read json only. Thank you, sehe. – Grzegorz Jan 24 '18 at 00:07
  • Because I couldn't find a previous answer doing it, I've added a BONUS take implementing a `query` function: [Live On Coliru](http://coliru.stacked-crooked.com/a/f673609ef975b0a6). To make it work generically with any character type, it is convenient to make the exception messages static: [working on a `const wiptree` just fine](http://coliru.stacked-crooked.com/a/330ccaf20af80c80) - note the correct support of case-insensitive keys. – sehe Jan 24 '18 at 01:16
  • Great job! Thank you so much! I hope this bonus will help many people. Thank you so much! – Grzegorz Jan 24 '18 at 14:35