5.4 KiB
Introduction
A typical example, loading a configuration file, might look like this:
YAML::Node config = YAML::LoadFile("config.yaml");
if (config["lastLogin"]) {
std::cout << "Last logged in: " << config["lastLogin"].as<DateTime>() << "\n";
}
const std::string username = config["username"].as<std::string>();
const std::string password = config["password"].as<std::string>();
login(username, password);
config["lastLogin"] = getCurrentDateTime();
std::ofstream fout("config.yaml");
fout << config;
Basic Parsing and Node Editing
All nodes in a YAML document (including the root) are represented by YAML::Node
. You can check what kind it is:
YAML::Node node = YAML::Load("[1, 2, 3]");
assert(node.Type() == YAML::NodeType::Sequence);
assert(node.IsSequence()); // a shortcut!
Collection nodes (sequences and maps) act somewhat like STL vectors and maps:
YAML::Node primes = YAML::Load("[2, 3, 5, 7, 11]");
for (std::size_t i=0;i<primes.size();i++) {
std::cout << primes[i].as<int>() << "\n";
}
// or:
for (YAML::const_iterator it=primes.begin();it!=primes.end();++it) {
std::cout << it->as<int>() << "\n";
}
primes.push_back(13);
assert(primes.size() == 6);
and
YAML::Node lineup = YAML::Load("{1B: Prince Fielder, 2B: Rickie Weeks, LF: Ryan Braun}");
for(YAML::const_iterator it=lineup.begin();it!=lineup.end();++it) {
std::cout << "Playing at " << it->first.as<std::string>() << " is " << it->second.as<std::string>() << "\n";
}
lineup["RF"] = "Corey Hart";
lineup["C"] = "Jonathan Lucroy";
assert(lineup.size() == 5);
Querying for keys does not create them automatically (this makes handling optional map entries very easy)
YAML::Node node = YAML::Load("{name: Brewers, city: Milwaukee}");
if (node["name"]) {
std::cout << node["name"].as<std::string>() << "\n";
}
if (node["mascot"]) {
std::cout << node["mascot"].as<std::string>() << "\n";
}
assert(node.size() == 2); // the previous call didn't create a node
If you're not sure what kind of data you're getting, you can query the type of a node:
switch (node.Type()) {
case Null: // ...
case Scalar: // ...
case Sequence: // ...
case Map: // ...
case Undefined: // ...
}
or ask directly whether it's a particular type, e.g.:
if (node.IsSequence()) {
// ...
}
Building Nodes
You can build YAML::Node
from scratch:
YAML::Node node; // starts out as null
node["key"] = "value"; // it now is a map node
node["seq"].push_back("first element"); // node["seq"] automatically becomes a sequence
node["seq"].push_back("second element");
node["mirror"] = node["seq"][0]; // this creates an alias
node["seq"][0] = "1st element"; // this also changes node["mirror"]
node["mirror"] = "element #1"; // and this changes node["seq"][0] - they're really the "same" node
node["self"] = node; // you can even create self-aliases
node[node["mirror"]] = node["seq"]; // and strange loops :)
The above node is now:
&1
key: value
&2 seq: [&3 "element #1", second element]
mirror: *3
self: *1
*3 : *2
How Sequences Turn Into Maps
Sequences can be turned into maps by asking for non-integer keys. For example,
YAML::Node node = YAML::Load("[1, 2, 3]");
node[1] = 5; // still a sequence, [1, 5, 3]
node.push_back(-3) // still a sequence, [1, 5, 3, -3]
node["key"] = "value"; // now it's a map! {0: 1, 1: 5, 2: 3, 3: -3, key: value}
Indexing a sequence node by an index that's not in its range will usually turn it into a map, but if the index is one past the end of the sequence, then the sequence will grow by one to accommodate it. (That's the only exception to this rule.) For example,
YAML::Node node = YAML::Load("[1, 2, 3]");
node[3] = 4; // still a sequence, [1, 2, 3, 4]
node[10] = 10; // now it's a map! {0: 1, 1: 2, 2: 3, 3: 4, 10: 10}
Converting To/From Native Data Types
Yaml-cpp has built-in conversion to and from most built-in data types, as well as std::vector
, std::list
, and std::map
. The following examples demonstrate when those conversions are used:
YAML::Node node = YAML::Load("{pi: 3.14159, [0, 1]: integers}");
// this needs the conversion from Node to double
double pi = node["pi"].as<double>();
// this needs the conversion from double to Node
node["e"] = 2.71828;
// this needs the conversion from Node to std::vector<int> (*not* the other way around!)
std::vector<int> v;
v.push_back(0);
v.push_back(1);
std::string str = node[v].as<std::string>();
To use yaml-cpp with your own data types, you need to specialize the YAML::convert<> template class. For example, suppose you had a simple Vec3
class:
struct Vec3 { double x, y, z; /* etc - make sure you have overloaded operator== */ };
You could write
namespace YAML {
template<>
struct convert<Vec3> {
static Node encode(const Vec3& rhs) {
Node node;
node.push_back(rhs.x);
node.push_back(rhs.y);
node.push_back(rhs.z);
return node;
}
static bool decode(const Node& node, Vec3& rhs) {
if(!node.IsSequence() || node.size() != 3) {
return false;
}
rhs.x = node[0].as<double>();
rhs.y = node[1].as<double>();
rhs.z = node[2].as<double>();
return true;
}
};
}
Then you could use Vec3
wherever you could use any other type:
YAML::Node node = YAML::Load("start: [1, 3, 0]");
Vec3 v = node["start"].as<Vec3>();
node["end"] = Vec3(2, -1, 0);