通过多级提升树迭代

时间:2017-07-19 18:47:49

标签: boost tree iteration ptree

我的树看起来像这样:

{
"Library":
{
    "L_ID": "1",
     "Book":
     {
         "B_ID": "1",
         "Title": "Moby Dick"
     }, 
     "Book":
     {
         "B_ID": "2",
         "Title": "Jurassic Park"
     }
},
"Library":
{
    "L_ID": "2",
     "Book":
     {
         "B_ID": "1",
         "Title": "Velocity"
     }, 
     "Book":
     {
        "B_ID": "2",
        "Title": "Creeper"
     }
}
}

我要做的是遍历库。当我找到我正在寻找的L_ID时,遍历书籍直到找到我正在寻找的B_ID。那时,我想访问该部分中的所有叶子。 即寻找图书馆2,书1,标题 注意:可能有更好的方法。

boost::property_tree::ptree libraries = config_.get_child("Library");
for (const auto &lib : libraries)
{
   if (lib.second.get<uint16_6>("L_ID") == 2)
   { 
       //at this point, i know i'm the correct library...
       boost::property_tree::ptree books = lib.get_child("Book");
       for (const auto &book : books)
       {
            if (book.second.get<uint16_t>("B_ID") == 1)
            {
                 std::string mybook = book.second.get<std::string>("Title");
            }
        }
   }
一旦我尝试查看我的第一个子树,我就失败了。这里出了什么问题?

2 个答案:

答案 0 :(得分:4)

首先,“JSON”存在严重缺陷。至少修复缺少的引号和逗号:

{
    "Library": {
        "L_ID": "1",
        "Book": {
            "B_ID": "1",
            "Title": "Moby Dick"
        },
        "Book": {
            "B_ID": "2",
            "Title": "Jurassic Park"
        }
    },
    "Library": {
        "L_ID": "2",
        "Book": {
            "B_ID": "1",
            "Title": "Velocity"
        },
        "Book": {
            "B_ID": "2",
            "Title": "Creeper"
        }
    }
}

接下来,你似乎很困惑。 get_child("Library")以该名称获取第一个子节点,而不是包含名为“Library”的子节点的节点(顺便说一下,它将是根节点)。

我可以建议添加一些抽象,也许还有一些设施可以通过某些名称/属性进行查询:

int main() {
    Config cfg;
    {
        std::ifstream ifs("input.txt");
        read_json(ifs, cfg.data_);
    }

    std::cout << "Book title: " << cfg.library(2).book(1).title() << "\n";
}

如您所见,我们假设Config类型可以找到库:

Library library(Id id) const { 
    for (const auto& lib : libraries())
        if (lib.id() == id) return lib;
    throw std::out_of_range("library");
}

什么是libraries()?我们将深入研究它,但让我们看一下它:

auto libraries() const {
    using namespace PtreeTools;
    return data_ | named("Library") | having("L_ID") | as<Library>();
}

该魔法应该被理解为“给我所有名为Library的节点,它们具有L_ID属性,但将它们包装在Library对象中”。暂时跳过细节,让我们看看Library对象,显然知道 books()

struct Library {
    ptree const& data_;

    Id id() const { return data_.get<Id>("L_ID"); } 

    auto books() const {
        using namespace PtreeTools;
        return data_ | named("Book") | having("B_ID") | as<Book>();
    }

    Book book(Id id) const { 
        for (const auto& book : books())
            if (book.id() == id) return book;
        throw std::out_of_range("book");
    }
};

我们在books()book(id)中看到相同的模式以查找特定项目。

魔术

魔术使用Boost Range适配器并潜伏在PtreeTools

namespace PtreeTools {

    namespace detail {
        // ... 
    }

    auto named(std::string const& name) { 
        return detail::filtered(detail::KeyName{name});
    }

    auto having(std::string const& name) { 
        return detail::filtered(detail::HaveProperty{name});
    }

    template <typename T>
    auto as() { 
        return detail::transformed(detail::As<T>{});
    }
}

这看起来很简单,对。那是因为我们站在Boost Range的肩膀上:

namespace detail {
    using boost::adaptors::filtered;
    using boost::adaptors::transformed;

接下来,我们只定义知道如何过滤特定ptree节点的谓词:

    using Value = ptree::value_type;

    struct KeyName {
        std::string const _target;
        bool operator()(Value const& v) const {
            return v.first == _target;
        }
    };

    struct HaveProperty {
        std::string const _target;
        bool operator()(Value const& v) const {
            return v.second.get_optional<std::string>(_target).is_initialized();
        }
    };

一个转换到我们的包装器对象:

    template <typename T>
        struct As {
            T operator()(Value const& v) const {
                return T { v.second };
            }
        };
}

全面演示

<强> Live On Coliru

#include <boost/property_tree/json_parser.hpp>
#include <boost/range/adaptors.hpp>

using boost::property_tree::ptree;

namespace PtreeTools {

    namespace detail {
        using boost::adaptors::filtered;
        using boost::adaptors::transformed;

        using Value = ptree::value_type;

        struct KeyName {
            std::string const _target;
            bool operator()(Value const& v) const {
                return v.first == _target;
            }
        };

        struct HaveProperty {
            std::string const _target;
            bool operator()(Value const& v) const {
                return v.second.get_optional<std::string>(_target).is_initialized();
            }
        };

        template <typename T>
            struct As {
                T operator()(Value const& v) const {
                    return T { v.second };
                }
            };
    }

    auto named(std::string const& name) { 
        return detail::filtered(detail::KeyName{name});
    }

    auto having(std::string const& name) { 
        return detail::filtered(detail::HaveProperty{name});
    }

    template <typename T>
    auto as() { 
        return detail::transformed(detail::As<T>{});
    }
}

struct Config {
    ptree data_;

    using Id = uint16_t;

    struct Book {
        ptree const& data_;
        Id id()             const  { return data_.get<Id>("B_ID"); } 
        std::string title() const  { return data_.get<std::string>("Title"); } 
    };

    struct Library {
        ptree const& data_;

        Id id() const { return data_.get<Id>("L_ID"); } 

        auto books() const {
            using namespace PtreeTools;
            return data_ | named("Book") | having("B_ID") | as<Book>();
        }

        Book book(Id id) const { 
            for (const auto& book : books())
                if (book.id() == id) return book;
            throw std::out_of_range("book");
        }
    };

    auto libraries() const {
        using namespace PtreeTools;
        return data_ | named("Library") | having("L_ID") | as<Library>();
    }

    Library library(Id id) const { 
        for (const auto& lib : libraries())
            if (lib.id() == id) return lib;
        throw std::out_of_range("library");
    }
};

#include <iostream>
int main() {
    Config cfg;
    {
        std::ifstream ifs("input.txt");
        read_json(ifs, cfg.data_);
    }

    std::cout << "Book title: " << cfg.library(2).book(1).title() << "\n";
}

打印:

Book title: Velocity

答案 1 :(得分:1)

@Sehe修复你的JSON在语法上是正确的,但我认为进一步比这更有意义。鉴于你所代表的数据,它会让伟大的更有意义的是拥有一个库数组,每个库都包含一系列书籍,给出如下数据:

{
    "Libraries": [
        {
            "L_ID": 1,
            "Books": [
                {
                    "B_ID": 1,
                    "Title": "Moby Dick"
                },
                {
                    "B_ID": 2,
                    "Title": "Jurassic Park"
                }
            ]
        },
        {
            "L_ID": 2,
            "Books": [
                {
                    "B_ID": 1,
                    "Title": "Velocity"
                },
                {
                    "B_ID": 2,
                    "Title": "Creeper"
                }
            ]
        }
    ]
}

然后,如果可能的话,我会选择一个真正适合手头工作的图书馆。 Boost属性树并不是真正的通用JSON库。如果你真的坚持的话,你可以把它推到那个角色,但至少对于你在问题中概述的内容,它会为你提供相当多的额外工作来获得你想要的东西。

就个人而言,我可能会使用nlohmann's JSON library代替。使用它,我们可以更直接地进行解决方案。基本上,一旦它解析了一个JSON文件,我们可以像处理来自标准库的普通集合一样处理结果 - 我们可以使用所有常规算法,基于范围的for循环等等。因此,我们可以加载一个类似于:

的JSON文件
using json=nlohmann::json;

std::ifstream in("libraries.json");
json lib_data;

in >> lib_data;

然后我们可以通过库查看特定ID号,其代码如下:

for (auto const &lib : libs["Libraries"])
    if (lib["L_ID"] == lib_num) 
        // we've found the library we want

寻找一本特定的书几乎完全相同:

for (auto const &book : lib["Books"])
   if (book["B_ID"] == book_num)
       // we've found the book we want

从那里打印标题看起来像:std::cout << book["Title"];

将这些放在一起,我们最终会得到类似这样的代码:

#include "json.hpp"
#include <fstream>
#include <iostream>

using json = nlohmann::json;

std::string find_title(json lib_data, int lib_num, int book_num) {
    for (auto const &lib : lib_data["Libraries"])
        if (lib["L_ID"] == lib_num) 
            for (auto const &book : lib["Books"])
                if (book["B_ID"] == book_num)
                    return book["Title"];

    return "";
}

int main() {

    std::ifstream in("libraries.json");

    json lib_data;

    in >> lib_data;

    std::cout << find_title(lib_data, 1, 2);
}

如果你真的想将每个JSON对象转换为C ++对象,你也可以很容易地做到这一点。它看起来像这样:

namespace library_stuff {
    struct Book {
        int B_ID;
        std::string title;    
    };

    void from_json(json &j, Book &b) {
        b.B_ID = j["B_ID"];
        b.title = j["Title"];
    }
}

因此,这里有两点:您必须将函数命名为from_json,并且必须在与其关联的struct / class相同的命名空间中定义它。有了这个脚手架,我们可以通过简单的赋值从JSON转换为struct,如下所示:

book b = lib_data["Libraries"][0]["Books"][1];

我认为这在特定情况下是否真的有用是非常值得怀疑的。