Yaml-cpp (new API): Problems mixing maps and scalars in a sequence

user3293204 picture user3293204 · Mar 17, 2014 · Viewed 7.1k times · Source

I have a very simple problem parsing a yaml file of this form:

- Foo
- Bar:
   b1: 5

I would like to parse the top level keys as strings namely "Foo" and "Bar". As you can see the first entry in the sequence is a scalar and the second is a map containing one key/value pair. Let's say I've loaded this YAML text into a node called config. I iterate over config in the following way:

YAML::Node::const_iterator n_it = config.begin();
for (; n_it != config.end(); n_it++) {
    std::string name;
    if (n_it->Type() == YAML::NodeType::Scalar)
        name = n_it->as<std::string>();
    else if (n_it->Type() == YAML::NodeType::Map) {
        name = n_it->first.as<std::string>();
    }
}

The problem is parsing the second "Bar" entry. I get the following yaml-cpp exception telling me I'm trying to access the key from a sequence iterator n_it.

YAML::InvalidNode: yaml-cpp: error at line 0, column 0: invalid node; this may result from using a map iterator as a sequence iterator, or vice-versa

If I change the access to this:

name = n_it->as<std::string>();

I get a different yaml-cpp exception which I guess is due to the fact that I'm trying to access the whole map as a std::string

YAML::TypedBadConversion<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >: yaml-cpp: error at line 0, column 0: bad conversion

Can somebody please explain to me what's going wrong?

Edit: new problems I'm still having problems with this api's handling of maps vs sequences. Now say I have the following structure:

foo_map["f1"] = "one";
foo_map["f2"] = "two";
bar_map["b1"] = "one";
bar_map["b2"] = "two";

I want this to be converted to the following YAML file:

Node: 
    - Foo:
      f1 : one
      f2 : two
    - Bar:
      b1 : one
      b2 : two

I would do so by doing:

node.push_back("Foo");
node["Foo"]["b1"] = "one";
...
node.push_back("Bar");

However at the last line node has now been converted from a sequence to a map and I get an exception. The only way I can do this is by outputting a map of maps:

Node:
    Foo:
      f1 : one
      f2 : two
    Bar:
      b1 : one
      b2 : two

The problem with this is if I cannot read back such files. If I iterate over Node, I'm unable to even get the type of the node iterator without getting an exception.

YAML::Node::const_iterator n_it = node.begin();

for (; n_it != config.end(); n_it++) {
        if (n_it->Type() == YAML::NodeType::Scalar) {
            // throws exception
        }
    }

This should be very simple to handle but has been driving me crazy!

Answer

Jesse Beder picture Jesse Beder · Mar 18, 2014

In your expression

name = n_it->first.as<std::string>();

n_it is a sequence iterator (since it's an iterator for your top-level node), which you've just established points to a map. That is,

YAML::Node n = *n_it;

is a map node. This map node (in your example) looks like:

Bar:
  b1: 5

In other words, it has a single key/value pair, with the key a string, and the value a map node. It sounds like you want the string key. So:

assert(n.size() == 1);  // Verify that there is, in fact, only one key/value pair
YAML::Node::const_iterator sub_it = n.begin();  // This iterator points to
                                                // the single key/value pair
name = sub_it->first.as<std::string>();