Search code examples
c++boostboost-propertytree

Setting up boost property_tree for a custom path type


I need to use boost::property_tree in my program. Right now I'm having hard time trying to figure out how to use it with a custom path type. The path type I want to have is Framework::CommonClientServer::InterfacePathChain_t

which is typedef'ed like this:

typedef std::vector<InterfaceID_t> InterfacePathChain_t;
typedef int InterfaceID_t;

So basically the path type should be std::vector<int>.

For example:

     0
    / \
   1   2
  /   / \
 3   4   5

These nodes will have the following paths:

0: {0}
1: {0, 0}
2: {0, 1}
3: {0, 0, 0}
4: {0, 1, 0}
5: {0, 1, 1}

I've defined the path_of struct in the following way:

namespace boost { namespace property_tree {
template<> class path_of<Framework::CommonClientServer::InterfacePathChain_t>
{
public:

    typedef Framework::CommonClientServer::InterfacePathChain_t key_type;

    struct type 
    {
        key_type path;  
    public:
        type(key_type pathVal)
        {
            path = pathVal;
        }

        std::string dump() const    
        {
            std::ostringstream oss;
            if (path.size() > 0)
            {
                key_type::const_iterator it = path.begin();
                oss << *it;
                ++it;
                while(it != path.end())
                {
                    oss << '.' << *it;
                    ++it;
                };
            }
            return oss.str();
        }

        bool empty() const  
        {
            return path.size() == 0;
        }

        key_type reduce()   
        {
            key_type res;
            res.push_back(*path.begin());
            path.erase(path.begin());
            return res;
        }

        bool single() const 
        {
            return path.size() == 1;
        }
    };
};
}}

After that I tried adding 2 nodes with different paths and IDs to the tree, but it doesn't seem to work. Actually, the first node is being added with the id I set (the path is {0}). But the second node (path is {0, 0} so it's supposed to be a child for node1) doesn't seem to be added. When I try to iterate through the tree, this happens:

MetaStorageTree tree;
Framework::CommonClientServer::InterfacePathChain_t path;
path.push_back(0);
MetaStorageTreeNode* node = new MetaStorageTreeNode(1);
tree.put(path, node);
Framework::CommonClientServer::InterfacePathChain_t path1;
path1.push_back(0);
path1.push_back(0);
MetaStorageTreeNode* node1 = new MetaStorageTreeNode(2);
tree.put(path1, node);
for (auto it : tree)
    {
        for (int i = 0; i < it.first.size(); i++)
        {
            std::cout << it.first[i];
        }
        std::cout << std::endl;
        if (it.second.empty()) //this returns true so node1 does not have a child
        {
            std::cout << "empty" << std::endl;
        }
    }

I believe I did something wrong with typedef'ing all the things in boost::property_tree I needed, but couldn't find any sufficient information on the topic, since most use of property_tree is dedicated to JSON parsing so standard std::string path type is used.


Solution

  • I think a logical misconception is at the root of this. You don't actually specialize a tree to use another path type.

    You specialize it for another key_type (in this case, you want it to be InterfaceID_t). The path type for use in queries is only implicitly derived and never stored.

    The path type can therefore not "be" InterfacePathChain_t, for it must be path_of<key_type>::type, and must adhere to the concept you implemented above. Instead, though, you can make the path_type be implicitly convertible from InterfacePathChain_t.

    Implementing all the required changes, we arrive at the following test program:

    using MetaStorageTree = boost::property_tree::basic_ptree<Framework::CommonClientServer::InterfaceID_t, std::string>;
    
    void dump_tree_for_debug(MetaStorageTree const&);
    
    int main() {
        MetaStorageTree tree;
    
        using Framework::CommonClientServer::InterfacePathChain_t;
        tree.put(InterfacePathChain_t{0}, "0");
        tree.put(InterfacePathChain_t{0, 0}, "1");
        tree.put(InterfacePathChain_t{0, 1}, "2");
        tree.put(InterfacePathChain_t{0, 0, 0}, "3");
        tree.put(InterfacePathChain_t{0, 1, 0}, "4");
        tree.put(InterfacePathChain_t{0, 1, 1}, "5");
    
        dump_tree_for_debug(tree);
    }
    
    • I elected to implement path_of<int>::type using a deque<int> which is is both more natural to the task, and underlines the fact that it does NOT need to be identical to InterfacePathChain_t.

    • I added output to XML; however, the serialization backends of PropertyTree assume that path_type::value_type be a valid stream character type. Since this is not the case for your chosen tree specialization, I have added a helper function to convert_weird_tree to standard ptree.

    Live On Coliru

    #include <boost/property_tree/ptree.hpp>
    #include <deque>
    #include <vector>
    
    namespace Framework { namespace CommonClientServer {
        using InterfaceID_t = int;
        using InterfacePathChain_t = std::vector<InterfaceID_t>;
    } }
    
    namespace boost { namespace property_tree {
        template<> struct path_of<Framework::CommonClientServer::InterfaceID_t>
        {
            typedef Framework::CommonClientServer::InterfaceID_t key_type;
            typedef std::deque<key_type> path_type;
    
            struct type 
            {
                path_type path;  
            public:
                // this allows us to easily convert paths to string in convert_weird_tree... (DEBUG)
                explicit type(Framework::CommonClientServer::InterfaceID_t id) : path { id } {}
    
                type(Framework::CommonClientServer::InterfacePathChain_t chain) : path(chain.begin(), chain.end()) {}
                type(path_type pathVal) : path(std::move(pathVal)) {}
    
                std::string dump() const    {
                    std::string r;
                    for (auto id : path)
                        r += std::to_string(id) + ".";
                    if (r.size())
                        r.resize(r.size()-1);
                    return r;
                }
    
                bool empty() const  { return path.empty(); }
                bool single() const { return path.size() == 1; }
    
                key_type reduce()   {
                    key_type res = path.front();
                    path.pop_front();
                    return res;
                }
            };
        };
    } }
    
    // Test code
    using MetaStorageTree = boost::property_tree::basic_ptree<Framework::CommonClientServer::InterfaceID_t, std::string>;
    
    void dump_tree_for_debug(MetaStorageTree const&);
    
    int main() {
        MetaStorageTree tree;
    
        using Framework::CommonClientServer::InterfacePathChain_t;
        tree.put(InterfacePathChain_t{0}, "0");
        tree.put(InterfacePathChain_t{0, 0}, "1");
        tree.put(InterfacePathChain_t{0, 1}, "2");
        tree.put(InterfacePathChain_t{0, 0, 0}, "3");
        tree.put(InterfacePathChain_t{0, 1, 0}, "4");
        tree.put(InterfacePathChain_t{0, 1, 1}, "5");
    
        dump_tree_for_debug(tree);
    }
    
    // FOR DEBUG/DEMO PURPOSES:
    #include <boost/property_tree/xml_parser.hpp>
    #include <iostream>
    
    template <typename WeirdTree, typename Path = typename WeirdTree::path_type>
    boost::property_tree::ptree convert_weird_tree(WeirdTree const& weird) {
        boost::property_tree::ptree normal;
    
        if (auto v = weird.template get_value_optional<std::string>()) {
            normal.put_value(*v);
        }
    
        for (auto& element : weird) {
            normal.add_child(Path{element.first}.dump(), convert_weird_tree(element.second));
        }
    
        return normal;
    }
    
    void dump_tree_for_debug(MetaStorageTree const& tree) {
        write_xml(std::cout, convert_weird_tree(tree), boost::property_tree::xml_writer_make_settings<std::string>(' ', 2));
    }
    

    Prints:

    <?xml version="1.0" encoding="utf-8"?>
    <0>
      0
      <0>
        1
        <0>3</0>
      </0>
      <1>
        2
        <0>4</0>
        <1>5</1>
      </1>
    </0>