2

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.

MarKS
  • 494
  • 6
  • 22

1 Answers1

5

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;
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • thank you for very well written answer! It cleared up so many confusions i had. I hope it will help lot of others. – MarKS Apr 20 '18 at 15:50