Search code examples
boosttreeiterationboost-propertytree

Iterate through multilevel boost tree


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??


Solution

  • 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