Iterate through multilevel boost tree

人走茶凉 提交于 2019-12-22 17:37:32

问题


With my tree looking like this:

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

What i am looking to do is iterate through the libraries. When i find the L_ID that i am looking for, iterate through the books until i find the B_ID i'm looking for. At that point, i'd like to access all the leaves in that section. I.e. looking for library 2, book 1, title Note: There's likely a better way than this.

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

I fail out as soon as i try looking into my first sub tree. What's going wrong here??


回答1:


For starters, the "JSON" is wildly flawed. At least fix the missing quotes and commas:

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

Next up, you seem to be confused. get_child("Library") gets the first child by that name, not a node containing child nodes called "Library" (that would be the root node, by the way).

May I suggest adding some abstraction, and perhaps some facilities to query by some names/properties:

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

As you can see, we assume a Config type that can find a library:

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

What is libraries()? We'll delve into it deeper, but lets just look at it for a second:

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

That magic should be read as "give me all nodes that are named Library, which have a L_ID property but wrap them in a Library object". Skipping on the detail for now, lets look at the Library object, which apparently knows about 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");
    }
};

We see the same pattern in books() and book(id) to find a specific item.

The Magic

The magic uses Boost Range adaptors and lurks in 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>{});
    }
}

That's deceptively simple, right. That's because we're standing on the shoulders of Boost Range:

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

Next, we only define the predicates that know how to filter for a specific ptree node:

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

And one transformation to project to our wrapper objects:

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

Full Live Demo

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

Prints:

Book title: Velocity



回答2:


@Sehe fixed your JSON to be syntactically correct, but I think it would make sense to go a bit further than that. Given the data you're representing, it would make a great deal more sense to have an array of libraries, each of which contains an array of books, giving data something like this:

{
    "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"
                }
            ]
        }
    ]
}

Then, if at all possible, I'd choose a library that's actually suited to the job at hand. Boost property tree isn't really intended as a general-purpose JSON library. You can push it into that role if you really insist, but at least for what you've outlined in the question, it puts you through quite a bit of extra work to get what you want.

Personally, I'd probably use nlohmann's JSON library instead. Using it, we can proceed a little more directly to a solution. Basically, once it's parsed a JSON file, we can treat the result a great deal like we would normal collections from the standard library--we can use all the normal algorithms, range-based for loops, and so on. So, we can load a JSON file something like:

using json=nlohmann::json;

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

in >> lib_data;

Then we can look through the libraries for a particular ID number with code something like this:

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

Looking for a particular book is nearly identical:

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

From there, printing out the title looks something like: std::cout << book["Title"];

Putting those together, we could end up with code something like this:

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

If you really want to convert each JSON object into a C++ object, you can do that fairly easily as well. It would look something like this:

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

So there are two points here: you must name the function from_json, and it must be defined in the same namespace as the struct/class it's associated with. With this bit of scaffolding in place, we can convert from JSON to struct with a simple assignment, something like this:

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

I'd consider it highly questionable whether that's really useful in this particular case though.



来源:https://stackoverflow.com/questions/45198739/iterate-through-multilevel-boost-tree

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!