Emit YAML iterating through boost property tree (recursively) - c++

I have a small but complex tree structure. Using, boost property tree as a container i am trying to iterate through the tree and subsequently emit it to the yaml file using yaml-cpp library.
For instance, i have a small nested property tree:
fibonacci:
type: series
entities:
golden_ratio:
ratio: 2.3
function:
power_series: 2
I want my yaml file to look exactly like this.
I wrote a recursive function to iterate through the tree and emit to yaml.
//Member variable
YAML::Emitter m_out
void iterator(const boost::property_tree::ptree& tree, const std::string& key)
{
for (const auto& item: tree)
{
if (item.second.data().empty()) //check if map node
{
m_out << YAML::BeginMap;
m_out << YAML::Key << item.first;
}
else if (!item.second.data().empty()) //else it is key/value pair
{
m_out << YAML::Key << item.first;
m_out << YAML::Value << item.second.data();
}
if (!item.second.empty()) //If the node has child
{
iterator(item.second, item.first);
}
}
}
I am calling the function with a emtpy key as iterator(root, ""). I know that the property tree works as key/value pairs, whereas, Yaml-cpp has node designations. In the code i am just trying to assume type of tree node based on value (no value - Map node, else - key/value node)
Apparently, my emitted yaml file doesn't possess the desired tree structure as presented above since my logic is wrong. I would like to make a recursive function which can iterate through any kind of tree and emit it to yaml file.
Is it possible to iterate tree and subsequently emit to yaml recursively? If yes, i would appreciate some ideas.

So I took your desired YAML and put it through an online converter to get a "reliable" ptree representation (which you interestingly left out of the question).
Then I proceeded to do a simple ptree roundtrip for sanity checks:
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
std::istringstream sample_json();
ptree sample_ptree();
int main() {
write_json(std::cout, sample_ptree());
}
std::istringstream sample_json() {
return std::istringstream(R"({
"fibonacci": {
"type": "series",
"entities": {
"golden_ratio": {
"ratio": 2.3
},
"function": {
"power_series": 2
}
}
}
})");
}
ptree sample_ptree() {
ptree pt;
{
auto stream = sample_json();
read_json(stream, pt);
}
return pt;
}
Prints
{
"fibonacci": {
"type": "series",
"entities": {
"golden_ratio": {
"ratio": "2.3"
},
"function": {
"power_series": "2"
}
}
}
}
to_yaml take #1
The simplest is, of course, to read the same JSON, and let yaml-cpp do the conversion:
auto stream = sample_json();
std::cout << YAML::Load(stream) << "\n";
Prints:
{fibonacci: {type: series, entities: {golden_ratio: {ratio: 2.3}, function: {power_series: 2}}}}
to_yaml take #2: pretty print
First off
naming is important. iterator doesn't describe the function, and clashes with the well-known concept from the standard library
the key argument is unused
you only ever BeginMap, how are you expecting a valid tree if you don't have EndMap anywhere in the code?
No global variables please. They make your code brittle (non-deterministic, non-idempotent, non-reentrant, not threadsafe etc.). Just pass that Emitter& as a parameter.
I'd make it MUCH simpler:
void to_yaml(ptree const& node, YAML::Emitter &m_out) {
if (node.empty()) {
m_out << YAML::Value << node.data();
} else {
m_out << YAML::BeginMap;
for (auto const&item : node) {
m_out << YAML::Key << item.first;
to_yaml(item.second, m_out);
}
m_out << YAML::EndMap;
}
}
Now, to have a convenient entry point, add an overload:
std::string to_yaml(ptree const& tree) {
YAML::Emitter out;
to_yaml(tree, out);
return out.c_str();
}
Now you can print the result by doing:
std::cout << to_yaml(sample_ptree()) << "\n";
Prints:
fibonacci:
type: series
entities:
golden_ratio:
ratio: 2.3
function:
power_series: 2
Full Listing
#include <iostream>
#include <boost/property_tree/json_parser.hpp>
using boost::property_tree::ptree;
std::istringstream sample_json();
ptree sample_ptree();
#include "yaml-cpp/yaml.h"
void to_yaml(ptree const& node, YAML::Emitter &m_out) {
if (node.empty()) {
m_out << YAML::Value << node.data();
} else {
m_out << YAML::BeginMap;
for (auto const&item : node) {
m_out << YAML::Key << item.first;
to_yaml(item.second, m_out);
}
m_out << YAML::EndMap;
}
}
std::string to_yaml(ptree const& tree) {
YAML::Emitter out;
to_yaml(tree, out);
return out.c_str();
}
int main() {
write_json(std::cout, sample_ptree());
{
auto stream = sample_json();
std::cout << YAML::Load(stream) << "\n";
}
std::cout << to_yaml(sample_ptree()) << "\n";
}
std::istringstream sample_json() {
return std::istringstream(R"({
"fibonacci": {
"type": "series",
"entities": {
"golden_ratio": {
"ratio": 2.3
},
"function": {
"power_series": 2
}
}
}
})");
}
ptree sample_ptree() {
ptree pt;
{
auto stream = sample_json();
read_json(stream, pt);
}
return pt;
}

Related

Iterate through PTree children w/o including tag name

I would like to know if there is a way to get the data in children nodes by iterating through a vector of the parent nodes. I have an XML file that I plan on changing frequently so hardcoding the attribute names is something I would like to avoid. Thus, I want to extract the data within my children nodes without stating the tag name of the nodes by using pt.get_child(myparentNodes). I have this in my main.
Any assistance is greatly appreciated!
vector<string> parentNodes;
ptree pt;
ifstream fileName("myxml");
read_xml(fileName, pt);
for(const ptree::value_type &parent : pt)
{
cout << parent.first << std::endl;
parentNodes.push_back(parent.first);
}
for(int i=0; i<parentNodes.size();i++)
{
BOOST_FOREACH(boost::property_tree::ptree::value_type const &node,pt.get_child(parentNodes[i]))
/* I'm having trouble properly accessing the children nodes here */
In your snippet (cleaned up a little):
std::vector<std::string> parentNodes;
for(auto const& parent : pt) {
std::cout << parent.first << std::endl;
parentNodes.push_back(parent.first);
}
appears to be collecting the names of tree nodes into parentNodes. However, this assumes that the names be unique, or non-empty.
Property names need not be unique, nor are they guaranteed to be non-empty. In fact arrays in Property Tree are frequently modeled as nodes with only unnamed child nodes.
Then you had trouble getting the children of corresponding nodes. Let's first do it the tedious way (again, assuming the names are unique):
for (size_t i = 0; i < parentNodes.size(); i++) {
auto& parent = pt.get_child(parentNodes[i]);
for (auto const& child : parent) {
std::cout << child.first << std::endl;
}
}
Of course using ranged-for is much easier:
for (auto const& name : parentNodes) {
auto& parent = pt.get_child(name);
for (auto const& child : parent) {
std::cout << child.first << std::endl;
}
}
Better Yet
You can avoid the assumptions about naming as well as the second loop and the vector storage:
for (auto const& parent : pt) {
std::cout << parent.first << std::endl;
auto& node = parent.second;
for (auto const& child : node) {
std::cout << child.first << std::endl;
}
}
This is because the iterator points to a pair of (key, value). In fact, on a recent compiler you can write the loop with structured bindings:
for (auto const& [name, node] : pt) {
std::cout << name << std::endl;
for (auto const& child : node) {
std::cout << child.first << std::endl;
}
}
still doing the same.
Generalizing
You said you want to make this generic. However, assuming two-layer hierarchy of parent/child relations does not strike me as "generic". I linked you to some examples that show generic traversal (e.g. looking for patterns throughout the whole tree) last time, e.g. Iterating on xml file with boost - A function from that example:
Live On Wandbox
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
using boost::property_tree::ptree;
static auto settings = boost::property_tree::xml_writer_make_settings<std::string>(' ', 4);
template <typename Out>
Out enumerate_nodes(ptree const& pt, ptree::path_type path, Out out) {
if (path.empty())
return out;
if (path.single()) {
auto name = path.reduce();
for (auto& child : pt) {
if (child.first == name)
*out++ = child.second;
}
} else {
auto head = path.reduce();
for (auto& child : pt) {
if (head == "*" || child.first == head) {
out = enumerate_nodes(child.second, path, out);
}
}
}
return out;
}
int main() {
std::ifstream fileName("input.xml");
ptree pt;
read_xml(fileName, pt);
for (auto const& [name, node] : pt) {
std::cout << name << std::endl;
for (auto const& child : node)
std::cout << child.first << std::endl;
}
std::vector<std::reference_wrapper<ptree const>> matched;
enumerate_nodes(pt, "root.parent2.child3", back_inserter(matched));
for (ptree const& match : matched)
std::cout << "Matched: " << match.get_value<std::string>() << "\n";
}
When using input.xml:
<?xml version="1.0"?>
<root>
<parent1>
<child1>parent1/child1</child1>
<child2>parent1/child2</child2>
<child3>parent1/child3</child3>
<child4>parent1/child4</child4>
</parent1>
<parent2>
<child1>parent2/child1</child1>
<child2>parent2/child2</child2>
<child3>parent2/child3</child3>
<child4>parent2/child4</child4>
</parent2>
<parent3>
<child1>parent3/child1</child1>
<child2>parent3/child2</child2>
<child3>parent3/child3</child3>
<child4>parent3/child4</child4>
</parent3>
<parent4>
<child1>parent4/child1</child1>
<child2>parent4/child2</child2>
<child3>parent4/child3</child3>
<child4>parent4/child4</child4>
</parent4>
</root>
Prints
root
parent1
parent2
parent3
parent4
Matched: parent2/child3

Recursively adding subtrees to boost property tree

I want to write a parameter server in C++ where I can recursively dump a tree of parameters into a property tree and then write it to a JSON file.
The dump function looks like this:
void Params::dump(string filename) {
// Create a root
pt::ptree root;
// Fill the root with parameters
mapToPt(curParams, root);
// Write to cout
pt::write_json(cout, root);
}
mapToPt is supposed to recursively go through the hierarchy of my parameter server and fill the property tree while doing so:
void Params::mapToPt(boost::shared_ptr<Params> curParams, pt::ptree &root) {
// Fill current root with parameters from curParams ParameterMap
map<string, boost::shared_ptr<Param> >::iterator it;
for ( it = curParams->getParamMap().begin(); it != curParams-getParamMap().end(); it++ ) {
root.put(it->first, it->second->getValue());
cout << "Add Parameter: \n";
cout << "Parameter name: " << it->first << "\n";
cout << "Parameter value: " << it->second->getValue() << "\n";
}
// Recursively go through all children to do the same to them
if(curParams->hasChildren()) { //ERROR LINE
map<string, boost::shared_ptr<Params> >::iterator it;
for ( it = curParams->getChildren().begin(); it != curParams-getChildren().end(); it++ ) {
pt::ptree new_tree;
root.add_child(it->second->getName(), new_tree);
cout << "Add Child: \n";
cout << "Child name: " << it->second->getName() << "\n";
mapToPt(it->second, new_tree);
}
}
}
My problem is that as soon as I go into recursion, errors occur at random lines that can not be the cause of the error. "basic_string::_M_construct null not valid" is the error message. I believe that I might access deleted content and that it might be due to the way I iterate through the property tree children.
Is my way of doing that wrong or is there another way to do it?
Thank you.
Why is mapToPt a member when it also expects a pointer to a Params instance?
Anyhoops, there's quite a bit of confusion.
At a design level, your Params type looks like it cannot decide whether it's a leaf node or not. Moreover, it suffers from "Quasi Classes" design, where getters essentially guarantee that there is no class invariant possible. In such cases, prefer to just have a struct with member fields.
Note, if you fail to return by reference from getParamMap() and getChildren() then you already have Undefined Behaviour in both loops, because the iterators then point into non-existent copies of containers.
You should check this. Also, see my working demo below
At the implementation level, this is causing you problems:
pt::ptree new_tree;
root.add_child(it->second->getName(), new_tree);
add_child inserts a copy of new_tree. Any future modification to new_tree has no effect. Instead, write:
pt::ptree& new_tree = root.add_child(it->second->getName(), {});
Here, new_tree becomes a reference to the actually added tree.
Attempted Fix
The style is still below my expectations. Personally I'd closely review the use of shared_ptr at all in this piece of code.
But this will probably help you along:
Live On Coliru
#include <boost/make_shared.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <iostream>
#include <map>
namespace pt = boost::property_tree;
struct Param {
std::string getValue() const { return "42"; }
};
struct Params {
using ParamMap = std::map<std::string, boost::shared_ptr<Param> >;
using Children = std::map<std::string, boost::shared_ptr<Params> >;
Params(std::string name = "") : _name(name) {}
std::string getName() const { return _name; }
ParamMap& getParamMap() { return _map; }
ParamMap const& getParamMap() const { return _map; }
bool hasChildren() const { return !_children.empty(); }
Children& getChildren() { return _children; }
Children const& getChildren() const { return _children; }
static void mapToPt(boost::shared_ptr<Params> curParams, pt::ptree &root);
private:
std::string _name;
ParamMap _map;
Children _children;
};
void Params::mapToPt(boost::shared_ptr<Params> curParams, pt::ptree &root) {
// Fill current root with parameters from curParams ParameterMap
std::map<std::string, boost::shared_ptr<Param> >::iterator it;
for (it = curParams->getParamMap().begin(); it != curParams->getParamMap().end(); it++) {
root.put(it->first, it->second->getValue());
//std::cout << "Add Parameter: \n";
//std::cout << "Parameter name: " << it->first << "\n";
//std::cout << "Parameter value: " << it->second->getValue() << "\n";
}
// Recursively go through all children to do the same to them
if (curParams->hasChildren()) {
for (auto it = curParams->getChildren().begin(); it != curParams->getChildren().end(); it++) {
pt::ptree& new_tree = root.add_child(it->second->getName(), {});
//std::cout << "Add Child: \n";
//std::cout << "Child name: " << it->second->getName() << "\n";
mapToPt(it->second, new_tree);
}
}
}
int main() {
auto a = boost::make_shared<Params>("rootparams");
a->getParamMap().emplace("one", boost::make_shared<Param>());
a->getParamMap().emplace("two", boost::make_shared<Param>());
a->getParamMap().emplace("three", boost::make_shared<Param>());
a->getChildren().emplace("child1", boost::make_shared<Params>("child1-name"))
.first->second->getParamMap().emplace("four", boost::make_shared<Param>());
a->getChildren().emplace("child2", boost::make_shared<Params>("child2-name"))
.first->second->getParamMap().emplace("five", boost::make_shared<Param>());
pt::ptree root;
a->mapToPt(a, root);
write_json(std::cout, root);
}
Prints
{
"one": "42",
"three": "42",
"two": "42",
"child1-name": {
"four": "42"
},
"child2-name": {
"five": "42"
}
}

boost ptree access first element with no path name

I am using boost library to manipulate a JSON string and I would like to access to a first element.
I was wondering if there where some convenient way to access a first element of ptree with no path name.
I do this, but I got no value :
namespace pt = boost::property_tree;
pt::ptree pt2;
string json = "\"ok\"";
istringstream is(json);
try
{
pt::read_json(is, pt2);
cout << pt2.get_child("").equal_range("").first->first.data() << endl;
}
catch (std::exception const& e)
{
cerr << e.what() << endl;
}
Solution:
replace cout << pt2.get_child("").equal_range("").first->first.data() << endl;
by cout << pt2.get_value<std::string>() << endl;
Firstly, Property Tree is not a JSON library.
Secondly, the input is not in the subset of JSON supported by the library (e.g.).
Thirdly, since the input results in a tree that has no child nodes, you should use the value of the root node itself.
Lastly, if you had wanted the first node, use ordered_begin()->second:
Live On Coliru
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>
void broken_input() {
boost::property_tree::ptree pt;
std::istringstream is("\"ok\"");
read_json(is, pt);
std::cout << "Root value is " << pt.get_value<std::string>() << std::endl;
}
void normal_tree() {
boost::property_tree::ptree pt;
pt.put("first", "hello");
pt.put("second", "world");
pt.put("third", "bye");
std::cout << pt.ordered_begin()->second.get_value<std::string>() << std::endl;
write_json(std::cout, pt);
}
int main() {
try {
broken_input();
normal_tree();
}
catch (std::exception const& e)
{
std::cerr << e.what() << std::endl;
}
}
Prints
Root value is ok
hello
{
"first": "hello",
"second": "world",
"third": "bye"
}
I would like to access to a first element.
It is impossible in general case, since JSON elements are not place-fixed by definition. The current first element can change its place after JSON transformations and a resulting JSON will be the same, although elements are reordered. Thus such API is not provided by BOOST.

MessagePack C++ - How to iterate through an unknown data structure?

I want to share structured data between C++ and Python languages using MessagePack like this one:
{
"t" : [ [t00,...,t0N], ... , [tM0,...,tMN] ],
"x" : [ x0,..,xN],
"P" : [ [P00, ..., P0N], ..., [PM0,...,PMN] ]
}
The number of variables is optional so in some cases I will have for example only:
{
"t" : [ [t00,...,t0N], ... , [tM0,...,tMN] ]
}
Decoding this in Python is pretty simple, my problem is to figure out
how to decode this in C++ if I don't know in advance the structure of
the data ? or the exact number of variables that I would have; is it
possible to iterate the structure in these cases?
I managed to handle a "fixed" data structure ( always with the same
number of variables ) defining a struct for example:
struct variables
{
std::vector< std::vector<double> > t;
std::vector< double > x;
std::vector< std::vector<double> > P;
MSPACK_DEFINE_MAP( t, x, P );
};
std::stringstream inBuffer;
.... (read data )
std::string str( inBuffer.str() );
msgpack::object_handle oh = msgpack::unpack( str.data(), str.size() );
msgpack::object deserialized = oh.get();
variables var;
deserialized.convert( var );
Is there a better way to accomplish this ?, how could manage optional
variables that could not appear in the structure ?; I repeat the
previous question: could I iterate an unknown data structure in C++?,
how ?
Thanks in advance!
Regards, Ernesto
There are two ways to treat unknown data structure.
The first way is using parse/visitor mechanism.
Here is an example:
#include <msgpack.hpp>
#include <sstream>
#include <iostream>
// This is a simple print example visitor.
// You can do any processing in your visitor.
struct my_visitor : msgpack::null_visitor {
bool start_map_key() {
processing_map_key = true;
return true;
}
bool end_map_key() {
processing_map_key = false;
return true;
}
bool start_array(uint32_t size) {
std::cout << "array (size:" << size << ")[" << std::endl;
return true;
}
bool end_array() {
std::cout << "]" << std::endl;
return true;
}
bool visit_str(const char* v, uint32_t size) {
if (processing_map_key) {
std::cout << "map key:" << std::string(v, size) << std::endl;
}
return true;
}
bool visit_positive_integer(uint64_t v) {
std::cout << "found value:" << v << std::endl;
return true;
}
bool processing_map_key = false;
std::string indent;
};
int main() {
// create test data
std::stringstream ss;
msgpack::packer<std::stringstream> pk(ss);
pk.pack_map(1);
pk.pack("t");
pk.pack_array(2);
pk.pack_array(3);
pk.pack(1);
pk.pack(2);
pk.pack(3);
pk.pack_array(3);
pk.pack(4);
pk.pack(5);
pk.pack(6);
// print data (for debug)
{
auto oh = msgpack::unpack(ss.str().data(), ss.str().size());
std::cout << oh.get() << std::endl;
}
// apply visitor
{
my_visitor mv;
msgpack::parse(ss.str().data(), ss.str().size(), mv);
}
}
Running demo: https://wandbox.org/permlink/3NrR4IMDIuLTk9e9
See https://github.com/msgpack/msgpack-c/wiki/v2_0_cpp_visitor.
The other way is using msgpack::type::variant or `msgpack::type::variant_ref.
The former copies data, you can update it. The latter doesn't copy data. You cannot update it.
This approach requires boost. So you need to define MSGPACK_USE_BOOST. I recommend defining as a compiler option.
// Boost is required
#define MSGPACK_USE_BOOST
#include <msgpack.hpp>
#include <sstream>
#include <iostream>
struct my_visitor:boost::static_visitor<void> {
void operator()(uint64_t v) const {
std::cout << "positive insteger:" << v << std::endl;
}
// const is required for map key because std::multimap's key (first) is const.
void operator()(std::string const& v) const {
std::cout << "string:" << v << std::endl;
}
void operator()(std::vector<msgpack::type::variant>& v) const {
std::cout << "array found" << std::endl;
for (auto& e : v) {
boost::apply_visitor(*this, e);
}
}
void operator()(std::multimap<msgpack::type::variant, msgpack::type::variant>& v) const {
std::cout << "map found" << std::endl;
for (auto& e : v) {
std::cout << "key:" << std::endl;
boost::apply_visitor(*this, e.first);
std::cout << "value:" << std::endl;
boost::apply_visitor(*this, e.second);
}
}
template <typename T>
void operator()(T const&) const {
std::cout << " match others" << std::endl;
}
};
int main() {
// create test data
std::stringstream ss;
msgpack::packer<std::stringstream> pk(ss);
pk.pack_map(1);
pk.pack("t");
pk.pack_array(2);
pk.pack_array(3);
pk.pack(1);
pk.pack(2);
pk.pack(3);
pk.pack_array(3);
pk.pack(4);
pk.pack(5);
pk.pack(6);
auto oh = msgpack::unpack(ss.str().data(), ss.str().size());
std::cout << oh.get() << std::endl;
msgpack::type::variant v = oh.get().as<msgpack::type::variant>();
boost::apply_visitor(my_visitor(), v);
}
Running demo: https://wandbox.org/permlink/HQwJjfwW8rLEMi0d
See https://github.com/msgpack/msgpack-c/wiki/v2_0_cpp_variant
Here are exampless:
https://github.com/msgpack/msgpack-c/blob/master/example/boost/msgpack_variant_capitalize.cpp
https://github.com/msgpack/msgpack-c/blob/master/example/boost/msgpack_variant_mapbased.cpp
Both ways can treat unpredictable data structure. You need to do some visitor processing. If the data structure is predictable some extent, your original approach is also good way.
Actually there is a simpler way, if you are dealing with maps (like stated in the question), not arrays.
msgpack::object_handle oh = msgpack::unpack(/* some data */);
std::map<std::string,msgpack::type::variant> map = obj.convert();
This way you will get a map with all the data, no need for a visitor or boost.

How to iterate a boost property tree?

I am know approaching to boost property tree and saw that it is a good feature of boost libs for c++ programming.
Well, I have one doubt? how to iterate a property tree using iterators or similar?
In reference there is just an example of browsing the tree through:
BOOST_FOREACH
But is there nothing more? Something like an stl-like container? It would be a better solution, speaking about code quality....
Here is what I came up with after much experimentation. I wanted to share it in the community because I couldn't find what I wanted. Everybody seemed to just post the answer from the boost docs, which I found to be insufficient. Anyhow:
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <string>
#include <iostream>
using namespace std;
using boost::property_tree::ptree;
string indent(int level) {
string s;
for (int i=0; i<level; i++) s += " ";
return s;
}
void printTree (ptree &pt, int level) {
if (pt.empty()) {
cerr << "\""<< pt.data()<< "\"";
}
else {
if (level) cerr << endl;
cerr << indent(level) << "{" << endl;
for (ptree::iterator pos = pt.begin(); pos != pt.end();) {
cerr << indent(level+1) << "\"" << pos->first << "\": ";
printTree(pos->second, level + 1);
++pos;
if (pos != pt.end()) {
cerr << ",";
}
cerr << endl;
}
cerr << indent(level) << " }";
}
return;
}
int main(int, char*[]) {
// first, make a json file:
string tagfile = "testing2.pt";
ptree pt1;
pt1.put("object1.type","ASCII");
pt1.put("object2.type","INT64");
pt1.put("object3.type","DOUBLE");
pt1.put("object1.value","one");
pt1.put("object2.value","2");
pt1.put("object3.value","3.0");
write_json(tagfile, pt1);
ptree pt;
bool success = true;
try {
read_json(tagfile, pt);
printTree(pt, 0);
cerr << endl;
}catch(const json_parser_error &jpe){
//do error handling
success = false
}
return success;
}
Here is the output:
rcook#rzbeast (blockbuster): a.out
{
"object1":
{
"type": "ASCII",
"value": "one"
},
"object2":
{
"type": "INT64",
"value": "2"
},
"object3":
{
"type": "DOUBLE",
"value": "3.0"
}
}
rcook#rzbeast (blockbuster): cat testing2.pt
{
"object1":
{
"type": "ASCII",
"value": "one"
},
"object2":
{
"type": "INT64",
"value": "2"
},
"object3":
{
"type": "DOUBLE",
"value": "3.0"
}
}
BOOST_FOREACH is just a convenient way for iterating that can be done by iterator, begin() and end()
Your_tree_type::const_iterator end = tree.end();
for (your_tree_type::const_iterator it = tree.begin(); it != end; ++it)
...
And since C++11 it's:
for (auto& it: tree)
...
I ran into this issue recently and found the answers incomplete for my need, so I came up with this short and sweet snippet:
using boost::property_tree::ptree;
void parse_tree(const ptree& pt, std::string key)
{
std::string nkey;
if (!key.empty())
{
// The full-key/value pair for this node is
// key / pt.data()
// So do with it what you need
nkey = key + "."; // More work is involved if you use a different path separator
}
ptree::const_iterator end = pt.end();
for (ptree::const_iterator it = pt.begin(); it != end; ++it)
{
parse_tree(it->second, nkey + it->first);
}
}
Important to note is that any node, except the root node can contain data as well as child nodes. The if (!key.empty()) bit will get the data for all but the root node, we can also start building the path for the looping of the node's children if any.
You'd start the parsing by calling parse_tree(root_node, "") and of course you need to do something inside this function to make it worth doing.
If you are doing some parsing where you don't need the FULL path, simply remove the nkey variable and it's operations, and just pass it->first to the recursive function.
An addition to the answer How to iterate a boost property tree? :
In the C++11 style range based for for (auto node : tree), each node is a std::pair<key_type, property_tree>
Whereas in the manually written iteration
Your_tree_type::const_iterator end = tree.end();
for (your_tree_type::const_iterator it = tree.begin(); it != end; ++it)
...
the iterator it is a pointer to such a pair. It's a tiny difference in usage. For example, to access the key, one would write it->first but node.first.
Posted as a new answer, because my proposed edit to the original answer was rejected with the suggestion to post a new answer.
BFS based print ptree traversal, May be used if we want to do some algorithmic manipulation
int print_ptree_bfs(ptree &tree) {
try {
std::queue<ptree*> treeQ;
std::queue<string> strQ;
ptree* temp;
if (tree.empty())
cout << "\"" << tree.data() << "\"";
treeQ.push(&tree);
//cout << tree.data();
strQ.push(tree.data());
while (!treeQ.empty()) {
temp = treeQ.front();
treeQ.pop();
if (temp == NULL) {
cout << "Some thing is wrong" << std::endl;
break;
}
cout << "----- " << strQ.front() << "----- " << std::endl;
strQ.pop();
for (auto itr = temp->begin(); itr != temp->end(); itr++) {
if (!itr->second.empty()) {
//cout << itr->first << std::endl;
treeQ.push(&itr->second);
strQ.push(itr->first);
} else {
cout<<itr->first << " " << itr->second.data() << std::endl;
}
}
cout << std::endl;
}
} catch (std::exception const& ex) {
cout << ex.what() << std::endl;
}
return EXIT_SUCCESS;
}