boost::property_tree::ptree 访问数组的第一个复杂元素

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

我的JSON是这样的:

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

我可以通过循环得到id的值"x",当it.firstid时退出:

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;
        }
    }
}

然而,我想要的是通过这样的方式获得 id 的值:

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

当然,这对我来说没有任何意义,因为我可能使用了错误的语法来访问应用程序数组的第一个元素。可能这必须以不同的方式一起完成。

我只发现了这种丑陋而危险的方法:

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

我真的不能这样使用它,因为当数组为空时它不会抛出异常,它会进行核心转储!

下面是整个程序,可以让任何想要帮助的人更容易:

#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;
}

正如文档所说,数组元素是具有 "" 个键的节点。

如果您在第一个元素之后,那么您很幸运:

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

路径中的..选择第一个空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";
}

奖金

如果您需要处理第一个元素以外的元素,请编写一个辅助函数。这将是一个好的开始:

#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);
}

下面是一些使用它的实时测试:

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";
    }
}

打印:

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